Detect incomplete subclass of abstract class, python - python

In Python, how can I differentiate between a concrete subclass and a subclass which is still abstract (i.e. not all abstract methods have been implemented)?
Consider the following:
import abc
class A(abc.ABC):
#abc.abstractmethod
def do_something(self):
pass
class B(A):
def do_something(self):
print('I am doing')
class C(A):
pass
for subclass in A.__subclasses__():
if is_concrete_class(subclass):
subclass().do_something()
else:
print('subclass is still abstract')
What is the implementation of is_concrete_class?
I could attempt to instantiate each subclass given by __subclasses__() and catch TypeError: Can't instantiate abstract class <class_name> with abstract methods <...>, but TypeError seems too broad of an exception to catch.
I didn't find anything useful in Python's ABC Library.

There's a function for that in the inspect module: inspect.isabstract(some_object) will tell you whether an object is an abstract class.
It returns True for an abstract class, and False for anything else, including abstract methods, classes that inherit from abc.ABC but are still concrete because don't have abstract methods, and objects that have nothing to do with abstract classes, like 3.

Related

Implementing an abstract class with a class method doesn't raise an exception

I am attempting to implement a method as both an abstract method and as a class method but it doesn't feel like any of the benefits of an abstract class are gained when doing so.
For example:
from abc import ABC, abstractmethod
class BasePipeline(ABC):
#classmethod
#abstractmethod
def consume_frame(cls):
pass
#abstractmethod
def consume_frame_two(self):
pass
class AnotherSubclass(BasePipeline):
#classmethod
def does_nothing(cls):
a = 1 + 1
# Call it.
AnotherSubclass.consume_frame()
This doesn't raise any exception and does not error out. I'd expect for it to say something along the lines of: consume_frame_two is not implemented and consume_frame is not implemented.
Not sure what the intended behavior is or if I'm just doing something wrong. I'd like for AnotherSubclass.consume_frame() to raise an exception if it isn't properly implemented as a class method.
Your code doesn't try to create an instance of the AnotherSubclass class. All it does is access the implementation of a classmethod that is marked as abstract. Python's ABC abstract classes are not intended to prevent that kind of access.
The abc module is intended to help you define a protocol or interface, a base class that sets expectations as to what attributes must be present on concrete objects that should be considered the same.
To that end, all that you can do with an ABC subclass is prevent instances to be created of any class in the class hierarchy that has at least one abstractmethod or abstractproperty attribute. From the #abc.abstractmethod() documentation:
A class that has a metaclass derived from ABCMeta cannot be instantiated unless all of its abstract methods and properties are overridden.
Any abstractmethod-decorated method can still be called; there is no mechanism to prevent this and it is actually a specific goal of the module that concrete implementations can use super().name() to access the implementation of an abstractmethod object. From the same source:
The abstract methods can be called using any of the normal ‘super’ call mechanisms
and
Note: Unlike Java abstract methods, these abstract methods may have an implementation. This implementation can be called via the super() mechanism from the class that overrides it. This could be useful as an end-point for a super-call in a framework that uses cooperative multiple-inheritance.
Any other attributes of the class can be used just the same as on other classes, including classmethod objects.
Under the covers, each ABCMeta metaclass gives each class you create with it a __abstractmethods__ attribute, which is a frozenset object with the names of any attribute on the class that has the __isabstractmethod__ attribute set to True, subclasses only have to use the same name as a parent abstract method object, setting it to an attribute that doesn't have __isabstractmethod__ set to true to remove that name from the set for that class. Python will then raise an exception when you try to create an instance of a class whose __abstractmethods__ is not empty.
If you need to lock down your class definitions further, then you'll have to come up with our own metaclass or other mechanism to implement those rules. For example, you could wrap classobject attributes in your own descriptor object that prevents calling a classmethod bound to a class with a non-empty __abstractmethods__ attribute.

How to create abstract classes using abc without using an abstract method?

Using abc, I can create abstract classes using the following:
from abc import ABC, abstractmethod
class A(ABC):
#abstractmethod
def foo(self):
print('foo')
class B(A):
pass
obj = B()
This will fail because B has not defined the method foo.
This mimics the abstract method functionality in Java.
I wanted to know if the abstract class functionality is also present in Python, where instantiation of a class is prevented without having any abstract methods.
The conventional way to create an abstract class in Python is to raise the built-in exception NotImplementedError.
class A(object):
def __init__(self):
raise NotImplementedError('abstract base class')
class B(A):
def __init__(self):
# don't call A.__init__ here.
pass
b = B()
# a = A() # This will fail.
Yes. You can.
If you want to not enforce method implementation:
Simply inherit from ABC but don't delcare a method abstract, so it needn't be implemented in its subclasses.
If you want the abstract class to enforce the implementation of all methods:
Decorate all methods.
If you want to enforce the implementation of a method that does not belong to an ABC:
Raise NotImplementedErrorin the method. This won't prevent instantiation, but usage. However, if you want to prevent it in the instantiation, you should rather use ABC's.
You can also delcare __init__ an abstractmethod, but generally this does not look very useful to me.

Inheritance in Python, requiring certain methods to be defined in subclasses

In Java, for example, you can make a class MyClass with certain methods that are specified but not implemented in MyClass, but must be implemented in any class MySubClass that inherits from MyClass. So basically there is some common functionality among all subclasses you want, so you put it in MyClass, and there is some functionality unique (but required) for each subclass, so you want it in each subclass. How can this behavior be achieved in Python?
(I know there are concise terms to describe what I'm asking, so feel free to let me know what these are and how I can better describe my question.)
A very basic example but the abc docs provide a few more
import abc
class Foo():
__metaclass__ = abc.ABCMeta
#abc.abstractmethod
def bar(self):
raise NotImplemented
class FooBar(Foo):
pass
f = FooBar()
TypeError: Can't instantiate abstract class FooBar with abstract methods bar
You can't require the implementation of a method in a subclass in a way that will break at compile-time, but the convention on writing a method on the base class that must be implemented in the subclasses is to raise NotImplementedError.
Something like this:
class MyBase(object):
def my_method(self, *args, **kwargs):
raise NotImplementedError("You should implement this method on a subclass of MyBase")
Then your subclasses can implement my_method, but this will break only when the method is called. If you have comprehensive unit tests, as you should, this won't be a problem.

Abstract method inheritance in Python

Let's assume that we have a Python class that makes use of the abc module to define an abstract attribute:
import abc
class A(object):
__metaclass__ = abc.ABCMeta
#abc.abstractproperty
def test_attribute(self):
raise NotImplementedError
Let's now consider to define B that subclasses from A by adding a new method (test_method()), and C that subclasses from B implementing the abstract method originally declared in A:
class B(A):
def test_method(self):
pass
class C(B):
def test_attribute(self):
# Implement abstract attribute
pass
Assuming that I would like to keep B abstract (non-instantiable), shall I redefine the abstract property (test_attribute) and the metaclass assignment also in B? Or is it enough to inherit them from A (as in the above code)?
I know that Python allows me to not redefine the abstract methods and thus inherit them from the parent class. Is this correct from a theoretical software engineering perspective?
I'm asking so because if I'm not wrong other languages (such as Java) do not allow inheritance of abstract methods without reimplementing them as abstract...
You've pretty much got all the code there, you can always test it and see if it works ... but as a spoiler, Your design is fine so long as C.test_attribute gets decorated with property.
If you try to make an instance of B, then you'll have problems since the whole abstract interface hasn't been created, but it is fine to create it as a base class for C (and presumably other classes later...)
e.g.:
import abc
class A(object):
__metaclass__ = abc.ABCMeta
#abc.abstractproperty
def foo(self):
pass
class B(A):
def bar(self):
return "bar"
class C(B):
#property
def foo(self):
return "foo"
print C().foo # foo
print C().bar() # bar
print B().foo # TypeError

Can one declare an abstract exception in Python?

I would like to declare a hierarchy of user-defined exceptions in Python. However, I would like my top-level user-defined class (TransactionException) to be abstract. That is, I intend TransactionException to specify methods that its subclasses are required to define. However, TransactionException should never be instantiated or raised.
I have the following code:
from abc import ABCMeta, abstractmethod
class TransactionException(Exception):
__metaclass__ = ABCMeta
#abstractmethod
def displayErrorMessage(self):
pass
However, the above code allows me to instantiate TransactionException...
a = TransactionException()
In this case a is meaningless, and should instead draw an exception. The following code removes the fact that TransactionException is a subclass of Exception...
from abc import ABCMeta, abstractmethod
class TransactionException():
__metaclass__ = ABCMeta
#abstractmethod
def displayErrorMessage(self):
pass
This code properly prohibits instantiation but now I cannot raise a subclass of TransactionException because it's not an Exception any longer.
Can one define an abstract exception in Python? If so, how? If not, why not?
NOTE: I'm using Python 2.7, but will happily accept an answer for Python 2.x or Python 3.x.
There's a great answer on this topic by Alex Martelli here. In essence, it comes down how the object initializers (__init__) of the various base classes (object, list, and, I presume, Exception) behave when abstract methods are present.
When an abstract class inherits from object (which is the default, if no other base class is given), its __init__ method is set to that of object's, which performs the heavy-lifting in checking if all abstract methods have been implemented.
If the abstract class inherits from a different base class, it will get that class' __init__ method. Other classes, such as list and Exception, it seems, do not check for abstract method implementation, which is why instantiating them is allowed.
The other answer provides a suggested workaround for this. Of course, another option that you have is simply to accept that the abstract class will be instantiable, and try to discourage it.
class TransactionException(Exception):
def __init__(self, *args, **kwargs):
raise NotImplementedError('you should not be raising this')
class EverythingLostException(TransactionException):
def __init__(self, msg):
super(TransactionException, self).__init__(msg)
try:
raise EverythingLostException('we are doomed!')
except TransactionException:
print 'check'
try:
raise TransactionException('we are doomed!')
except TransactionException:
print 'oops'
My implementation for an abstract exception class, in which the children of the class work out of the box.
class TransactionException(Exception):
def __init__(self):
self._check_abstract_initialization(self)
#staticmethod
def _check_abstract_initialization(self):
if type(self) == TransactionException:
raise NotImplementedError("TransactionException should not be instantiated directly")
class AnotherException(TransactionException):
pass
TransactionException() # NotImplementedError: TransactionException should not be instantiated directly
AnotherException # passes
Here's a helper function that can be used in such scenario:
def validate_abstract_methods(obj):
abstract_methods = []
for name in dir(obj):
value = getattr(obj, name, None)
if value is not None and getattr(value, '__isabstractmethod__', False):
abstract_methods.append(name)
if abstract_methods:
abstract_methods.sort()
raise TypeError(f"Can't instantiate abstract class {obj.__class__.__name__} with abstract methods {', '.join(abstract_methods)}")
This function roughly does the same thing as abc.ABC class - you just need to call it from your class' __init__ method.

Categories

Resources