I'm trying to understand metaclass black magic in Python. AFAIK, metaclasses can be used, for example, to ensure that some method is implemented in derived class, but I have a problem grandchildrens . It seems that I need to explicitly implement all required derived methods even if there is no reason(?) to do that.
Look at this:
~ $ ipython
Python 3.6.5 (default, Apr 14 2018, 13:17:30)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.3.1 -- An enhanced Interactive Python. Type '?' for help.
In [1]: class Meta(type):
...: def __new__(cls, name, bases, body):
...: if 'some_method' not in body:
...: raise AttributeError('some_method should be implemented')
...: return super().__new__(cls, name, bases, body)
...:
...: class Base(metaclass=Meta):
...: def some_method(self):
...: print('Yay')
...:
...: class Derived(Base):
...: pass
...:
...:
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-1-51c072cc7909> in <module>()
9 print('Yay')
10
---> 11 class Derived(Base):
12 pass
<ipython-input-1-51c072cc7909> in __new__(cls, name, bases, body)
2 def __new__(cls, name, bases, body):
3 if 'some_method' not in body:
----> 4 raise AttributeError('some_method should be implemented')
5 return super().__new__(cls, name, bases, body)
6
AttributeError: some_method should be implemented
As far I understand this should not happen, because some_method
should be derived from Base
class like here:
In [3]: class Base:
...: def some_method(self):
...: print('Yay')
...:
...: class Derived(Base):
...: pass
...:
...:
In [4]: Derived().some_method()
Yay
This is expected? If yes, could you give me some hint how to fix this behaviour, so I wouldn't need to reimplement methods?
You seem to be trying to reinvent the wheel that is the abc
module and its @abstractmethod
decorator. I'm not sure why you want to do that, but if you do, you should look at its source (which is linked from the docs).
Your solution checks that the required name is implemented in the body of the class. That won't be true for inherited methods, but you seem to know that, so I won't explain. What you want to know is: what can you do instead? You need to check for inherited methods explicitly.
The way abc
does it is pretty simple: before checking whether all abstract methods are implemented, it calls getattr
on each base
. (The best way to simulate what getattr
will do on the class is to call getattr
, after all.) Anything that's found there doesn't need to appear in the body.
Your use case—with a static single required method instead of a dynamic set of methods that have to be harvested from the output of a decorator—is even simpler:
if (not any(hasattr(base, 'some_method') for base in bases)
and 'some_method' not in body):
Inheritance isn't something that injects a definition into a class. Rather, it allows the lookup for an attribute to proceed beyond the immediate class if it is not defined locally.
In the absence of your metaclass, a call to Defined().some_method()
proceeds roughly as follows:
Defined
is created. some_method
is looked for that instance's __dict__
attribute, and is not found. some_method
is looked for in Derived.__dict__
and not found. some_method
is searched for in the __dict__
attributes of the classes in Derived
's based classes, starting with Base
. Base.__dict__['some_method']
is found, and its value is called with the instance of Derived
as its implicit first argument. With your metaclass, Derived
is created with the same metaclass used by its base class, meaning Meta.__new__
(not type.__new__
) is used to create Derived
. Since some_method
is not in the body of the class statement, it does not appear in the dict
object passed to Meta.__new__
, resulting in the AttributeError
being raised. (Note that you should be raising NotImplemented
, not AttributeError
, as you aren't attempting to access any particular attribute yet.)
body
contains what is defined in the class you're defining. In your case, some_method
isn't defined in Derived
, it doesn't exist in it.
When you use Derived().some_method()
(or even Derived.some_method
), it will perform a lookup. It will not find it in Derived, but then it will try the parent classes, aka the bases, using the MRO (Method Resolution Order).
See the params in def __new__(cls, name, bases, body):
, the bases are separate from body
which is what you defined in your class, before MRO kicks in.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.