I was coding a decoration (for curiosity's sake) to make a class abstract in python. So far it looked like it was going to work, but I got an unexpected behavior.
The idea for the decoration looks like this:
from abc import ABCMeta, abstractmethod
def abstract(cls):
cls.__metaclass__ = ABCMeta
return cls
Then, when using this decoration, it's only needed to define a abstract method
@abstract
class Dog(object):
@abstractmethod
def bark(self):
pass
But when I test, I was able to instantiate a Dog object:
d = Dog()
d.bark() //no errors
Dog.__metaclass__ //returned "<class 'abc.ABCMeta'>"
When testing assigning __metaclass__
directly, it behave as expected:
class Dog(object):
__metaclass__ = ABCMeta
@abstractmethod
def bark(self):
pass
Testing:
d = Dog()
"Traceback (most recent call last):
File "<pyshell#98>", line 1, in <module>
d = Dog()
TypeError: Can't instantiate abstract class Dog with abstract methods bark"
Why is this happening?
abstractmethod. A decorator indicating abstract methods. Using this decorator requires that the class's metaclass is ABCMeta or is derived from it. A class that has a metaclass derived from ABCMeta cannot be instantiated unless all of its abstract methods and properties are overridden.
abstractmethod is oftentimes used as a decorator indicating abstract methods. The abstract class and the abstractmethod are typically used to regulate data and class interfaces.
Abstract classes cannot be instantiated, and require subclasses to provide implementations for the abstract methods.
To define an abstract method we use the @abstractmethod decorator of the abc module. It tells Python that the declared method is abstract and should be overridden in the child classes. We just need to put this decorator over any function we want to make abstract and the abc module takes care of the rest.
The python reference states:
When the class definition is read, if
__metaclass__
is defined then the callable assigned to it will be called instead of type(). This allows classes or functions to be written which monitor or alter the class creation process
The important part is when the class definition is read, that means you can't change the metaclass afterwards, and that's just what a decorator tries to do, as it's just syntactic sugar.
@some_decorator
class SomeClass(object):
pass
is the same as:
class SomeClass(object):
pass
SomeClass = some_decorator(SomeClass)
So when the decorator is called, the class definition has already been read.
I have been able to reproduce your problem.
ABCMeta.__new__ is never called using your decorator. When you really define it as metaclass of Dog, it gets called. This is because the Dog class has already been defined, so adding ABCMeta after won't actually make any difference, because its new not being called at definition time, won't register the Dog class as an abstract and won't discover its abstract methods. I invite you to read the ABCMeta.new code and you'll see what it does.
However, I found this solution, to still make your decorator work:
def abstract(cls):
return ABCMeta(cls.__name__, cls.__bases__, dict(cls.__dict__))
This works now as expected. Meaning that you must subclass Dog
to be able to call bark
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With