简体   繁体   English

元类:为什么方法不继承自基类?

[英]Metaclasses: why method is not inherited from Base class?

I'm trying to understand metaclass black magic in Python. 我正在尝试了解Python中的元类黑魔法 AFAIK, metaclasses can be used, for example, to ensure that some method is implemented in derived class, but I have a problem grandchildrens . AFAIK可以使用元类,例如,以确保在派生类中实现某些方法,但是我有一个孙子辈 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: 据我了解,这不应该发生,因为some_method应该像下面这样从Base类派生:

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. 您似乎正在尝试重新发明abc模块及其@abstractmethod装饰器。 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 . abc的方法非常简单:在检查是否实现了所有抽象方法之前,它会在每个base上调用getattr (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. (毕竟,模拟getattr将在类上执行的操作的最佳方法是调用getattr 。)在那里找到的任何内容都不需要出现在主体中。

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().some_method()的调用大致如下:

  1. An instance of Defined is created. 创建Defined的实例。
  2. An attribute named some_method is looked for that instance's __dict__ attribute, and is not found. 查找该实例的__dict__属性的名为some_method的属性,但未找到。
  3. some_method is looked for in Derived.__dict__ and not found. 在“ Derived.__dict__ some_method ,但未找到。
  4. some_method is searched for in the __dict__ attributes of the classes in Derived 's based classes, starting with Base . Derived的基于类的类的__dict__属性中搜索some_method ,从Base开始。
  5. Base.__dict__['some_method'] is found, and its value is called with the instance of Derived as its implicit first argument. Base.__dict__['some_method'] ,并使用Derived实例作为其隐式第一个参数来调用其值。

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 . 随着你的元类, Derived用其基类中使用的相同元类来创建,意Meta.__new__ (不type.__new__ )是用于创建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. 由于some_method不在类语句的主体中,因此它不会出现在传递给Meta.__new__dict对象中,从而引发AttributeError (Note that you should be raising NotImplemented , not AttributeError , as you aren't attempting to access any particular attribute yet.) (请注意,由于您尚未尝试访问任何特定属性,因此应引发NotImplemented而不是AttributeError 。)

body contains what is defined in the class you're defining. body包含您要定义的类中定义的内容。 In your case, some_method isn't defined in Derived , it doesn't exist in it. 在您的情况下, some_method没有在Derived定义,它不存在。

When you use Derived().some_method() (or even Derived.some_method ), it will perform a lookup. 当您使用Derived().some_method() (甚至Derived.some_method )时,它将执行查找。 It will not find it in Derived, but then it will try the parent classes, aka the bases, using the MRO (Method Resolution Order). 它不会在“派生”中找到它,但是随后它将使用MRO(方法解析顺序)尝试父类(也称为基类)。

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. 请参阅def __new__(cls, name, bases, body):在MRO启动之前,bases与body是分开的,这是您在类中定义的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM