Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python abstract decoration not working

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?

like image 640
Hydren Avatar asked Sep 17 '15 21:09

Hydren


People also ask

What is ABC Abstractmethod in Python?

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.

What does Abstractmethod decorator do Python?

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.

How do you instantiate an abstract class in Python?

Abstract classes cannot be instantiated, and require subclasses to provide implementations for the abstract methods.

How do you implement an abstract method in Python?

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.


2 Answers

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.

like image 106
mata Avatar answered Oct 03 '22 18:10

mata


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.

like image 32
DevLounge Avatar answered Oct 03 '22 17:10

DevLounge