I have been experimenting a little with the abc
module in python. A la
>>> import abc
In the normal case you expect your ABC class to not be instantiated if it contains an unimplemented abstractmethod
. You know like as follows:
>>> class MyClass(metaclass=abc.ABCMeta):
... @abc.abstractmethod
... def mymethod(self):
... return -1
...
>>> MyClass()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class MyClass with abstract methods mymethod
OR for any derived Class. It all seems to work fine until you inherit from something ... say dict
or list
as in the following:
>>> class YourClass(list, metaclass=abc.ABCMeta):
... @abc.abstractmethod
... def yourmethod(self):
... return -1
...
>>> YourClass()
[]
This is surprising because type
is probably the primary factory or metaclass -ish thing anyway or so I assume from the following.
>>> type(abc.ABCMeta)
<class 'type'>
>>> type(list)
<class 'type'>
From some investigation I found out that it boils down to something as simple as adding an __abstractmethod__
attribute to the class' object
and rest happens by itself:
>>> class AbstractClass:
... pass
...
>>> AbstractClass.__abstractmethods__ = {'abstractmethod'}
>>> AbstractClass()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class AbstractClass with abstract methods abstractmethod
So one can simply avoid the check by intentionally overriding the __new__
method and clearing out __abstractmethods__
as in below:
>>> class SupposedlyAbstractClass(metaclass=abc.ABCMeta):
... def __new__(cls):
... cls.__abstractmethods__ = {}
... return super(AbstractClass, cls).__new__(cls)
... @abc.abstractmethod
... def abstractmethod(self):
... return -1
...
>>> SupposedlyAbstractClass()
<__main__.SupposedlyAbstractClass object at 0x000001FA6BF05828>
This behaviour is the same in Python 2.7 and in Python 3.7 as I have personally checked. I am not aware if this is the same for all other python implementations.
Finally, down to the question ... Why has this been made to behave like so? Is it wise we should never make abstract classes out of list
, tuple
or dict
? or should I just go ahead and add a __new__
class method checking for __abstractmethods__
before instantiation?
The problem
If you have the next class:
the problem is that and object of
Foo
can be created without any error becauseFoo.__new__(Foo)
delegates the call directly tolist.__new__(Foo)
instead ofABC.__new__(Foo)
(which is responsible of checking that all abstract methods are implemented in the class that is going to be instantiated)We could implement
__new__
on Foo and try to callABC.__new__
:But he next error is raised:
This is due to
ABC.__new__(Foo)
invokesobject.__new__(Foo)
which seems that is not allowed whenFoo
inherits fromlist
A possible solution
You can add additional code on
Foo.__new__
in order to check that all abstract methods in the class to be instantiated are implemented (basically do the job ofABC.__new__
).Something like this:
Now
Foo()
raises an error. But the next code runs without any issue: