Can one declare an abstract exception in Python? - 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.

Related

Detect incomplete subclass of abstract class, 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.

Enforce/Define python classes with only the specified attributes [duplicate]

I have two classes that are supposed to implement the same test cases for two independent libraries (let's call them LibA and LibB). So far I define the test methods to be implemented in an abstract base class which ensures that both test classes implement all desired tests:
from abc import ABC, abstractmethod
class MyTests(ABC):
#abstractmethod
def test_foo(self):
pass
class TestsA(MyTests):
def test_foo(self):
pass
class TestsB(MyTests):
def test_foo(self):
pass
This works as expected, but what may still happen is that someone working on LibB accidentally adds a test_bar() method to TestB instead of the base class. The missing test_bar() in the TestA class would go unnoticed in that case.
Is there a way to prohibit the addition of new methods to an (abstract) base class? The objective is to force the addition of new methods to happen in the base class and thus force the implementation of new methods in all derived classes.
Yes. It can be done through a metaclass, or from Python 3.6 onwards, with a check in __init_subclass__ of the baseclass.
__init_sublass__ is a special method called by the language each time a subclass is instantiated. So it can check if the new class have any method that is not present in any of the superclasses and raise a TypeError when the subclass is declared. (__init_subclass__ is converted to a classmethod automatically)
class Base(ABC):
...
def __init_subclass__(cls, *args, **kw):
super().__init_subclass__(*args, **kw)
# By inspecting `cls.__dict__` we pick all methods declared directly on the class
for name, attr in cls.__dict__.items():
attr = getattr(cls, name)
if not callable(attr):
continue
for superclass in cls.__mro__[1:]:
if name in dir(superclass):
break
else:
# method not found in superclasses:
raise TypeError(f"Method {name} defined in {cls.__name__} does not exist in superclasses")
Note that unlike the TypeError raised by non-implemented abstractmethods, this error is raised at class declaration time, not class instantiation time. If the later is desired, you have to use a metaclass and move the check to its __call__ method - however that complicates things, as if one method is created in an intermediate class, that was never instantiated, it won't raise when the method is available in the leaf subclass. I guess what you need is more along the code above.

Prohibit addition of new methods to a Python child class

I have two classes that are supposed to implement the same test cases for two independent libraries (let's call them LibA and LibB). So far I define the test methods to be implemented in an abstract base class which ensures that both test classes implement all desired tests:
from abc import ABC, abstractmethod
class MyTests(ABC):
#abstractmethod
def test_foo(self):
pass
class TestsA(MyTests):
def test_foo(self):
pass
class TestsB(MyTests):
def test_foo(self):
pass
This works as expected, but what may still happen is that someone working on LibB accidentally adds a test_bar() method to TestB instead of the base class. The missing test_bar() in the TestA class would go unnoticed in that case.
Is there a way to prohibit the addition of new methods to an (abstract) base class? The objective is to force the addition of new methods to happen in the base class and thus force the implementation of new methods in all derived classes.
Yes. It can be done through a metaclass, or from Python 3.6 onwards, with a check in __init_subclass__ of the baseclass.
__init_sublass__ is a special method called by the language each time a subclass is instantiated. So it can check if the new class have any method that is not present in any of the superclasses and raise a TypeError when the subclass is declared. (__init_subclass__ is converted to a classmethod automatically)
class Base(ABC):
...
def __init_subclass__(cls, *args, **kw):
super().__init_subclass__(*args, **kw)
# By inspecting `cls.__dict__` we pick all methods declared directly on the class
for name, attr in cls.__dict__.items():
attr = getattr(cls, name)
if not callable(attr):
continue
for superclass in cls.__mro__[1:]:
if name in dir(superclass):
break
else:
# method not found in superclasses:
raise TypeError(f"Method {name} defined in {cls.__name__} does not exist in superclasses")
Note that unlike the TypeError raised by non-implemented abstractmethods, this error is raised at class declaration time, not class instantiation time. If the later is desired, you have to use a metaclass and move the check to its __call__ method - however that complicates things, as if one method is created in an intermediate class, that was never instantiated, it won't raise when the method is available in the leaf subclass. I guess what you need is more along the code above.

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.

Class that should not be instantiated

I want to create a class hierarchy in which I have a class Block which can be instantiated by itself. Then I have a class List which inherits from Block and contains methods common to all lists, and finally I have classes OrderedList, LableledList etc that inherit from List. I want people to be able to instantiate OrderedList etc, but not List.
In other words, you can instantiate a plain Block and you can instantiate an OrderedList that inherits from List that inherits from Block, but you can't instantiate List.
All attempts to Google this lead to Abstract Base Classes, but none provides and example that fits this case and I am having trouble extrapolating.
The following conversation with the interpreter should show how this is possible. After inheriting from the Abstract Base Class with Block, you only need to mark the initializer on List as being an abstractmethod. This will prevent instantiation of the class without causing problems for child classes.
>>> import abc
>>> class Block(abc.ABC):
def __init__(self, data):
self.data = data
>>> class List(Block):
#abc.abstractmethod
def __init__(self, data, extra):
super().__init__(data)
self.extra = extra
>>> class OrderedList(List):
def __init__(self, data, extra, final):
super().__init__(data, extra)
self.final = final
>>> instance = Block(None)
>>> instance = List(None, None)
Traceback (most recent call last):
File "<pyshell#42>", line 1, in <module>
instance = List(None, None)
TypeError: Can't instantiate abstract class List with abstract methods __init__
>>> instance = OrderedList(None, None, None)
>>>
Your List class should have ABCMeta as a metaclass and make the init methods abstract.
from abc import ABCMeta
class List(metaclass=ABCMeta):
#abstractmethod
__init__():
pass
https://docs.python.org/3/library/abc.html
Inherit from ABC located in the abc module and make methods that are implemented in base classes (that inherit from List) #abstractmethods (a decorator located in abc):
from abc import ABC, abstractmethod
class List(ABC, Block):
#abstractmethod
def size(self):
return 0
Having an ABC with #abstractmethods defined forbids from instantiation.
The "one and obvious" way to do this is to use ABCMeta and mark some methods as abstract as documented on other answers.
But if in your case you don't have a set of methods that one has to override in a mandatory way (let's suppose your __init__ is reusable in some cases, and other of the list methods as well):
In that case you can create a __new__ method that checks if the clas being istantiated is the own class, and raises. To do that, you have to use teh magic __class__ variable that is documentend only in corners of Python docs - if you as much as use the __class__ variable in any method body, it will automatically take the value of the class where it was declared, at run time. It is part of the parameterless super mechanism of Python 3.
Thus:
class List(Block):
def __new__(cls, *args, **kw):
if cls is __class__:
raise TypeError(cls.__name__ + " can't be directly instantiated")
return super().__new__(cls, *args, **kw)
Btw, you should give preference for the ABCMeta abstractmethods if your pattern allows it. Note that if your classes use a custom metaclass it will conflict with the ABCMeta as well - so you may need to resort to this as well
(If you don't further customize __new__, then you'd better not pass args and kw upstream on the __new__ method: Python's object.__new__ ignore extra args if __init__ is defined but __new__ is not in the subclasses - but if both are defined it raises an error)

Categories

Resources