元类:为什么方法不是从Base类继承的?

时间:2018-05-13 20:19:24

标签: python python-3.x metaclass

我正在尝试理解Python中的元类黑魔法。例如,可以使用AFAIK,元类来确保在派生类中实现某些方法,但我遇到了问题 grandchildrens 。看来我需要明确地实现所有所需的派生方法,即使没有理由(?)这样做。

看看这个:

if(agent instanceof Children)
    agent.priority=1;
else if(agent instanceof Adult)
    agent.priority=2;

据我所知,这不应该发生,因为~ $ 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 应该来自some_method类,如下所示:

Base

这是预期的吗?如果是,你能否给我一些提示如何修复这种行为,所以我不需要重新实现方法?

3 个答案:

答案 0 :(得分:2)

您似乎正在尝试重新发明abc模块及其@abstractmethod装饰器的轮子。我不确定你为什么要这样做,但是如果你这样做,你应该看看its source(从文档中链接)。

您的解决方案会检查所需的名称是否已在类的正文中实现。对于继承的方法来说,这不是真的,但你似乎知道这一点,所以我不会解释。你想知道的是:你能做什么呢?您需要明确检查继承的方法。

abc的方式非常简单:在检查是否所有抽象方法都已实现之前,它会在每个getattr上调用base。 (模拟getattr将在课堂上执行的操作的最佳方法是调用getattr。)任何在那里找到的东西都不需要出现在正文中。

您的用例 - 使用静态单个必需方法而不是必须从装饰器的输出中收集的动态方法集 - 甚至更简单:

if (not any(hasattr(base, 'some_method') for base in bases)
    and 'some_method' not in body):

答案 1 :(得分:1)

继承不是将定义注入类的东西。相反,它允许属性的 lookup 继续超出直接类,如果它没有在本地定义。

如果没有您的元类,对Defined().some_method()的调用大致如下:

  1. 创建了Defined的实例。
  2. 查找该实例的some_method属性名为__dict__的属性,但未找到该属性。
  3. some_methodDerived.__dict__中找到但未找到。
  4. some_method__dict__基于Derived类的Base类中搜索,从Base.__dict__['some_method']开始。
  5. 找到
  6. Derived,并调用其值   将Derived的实例作为其隐含的第一个参数。
  7. 使用您的元类,Meta.__new__创建时使用其基类使用的相同元类,这意味着type.__new__(不是Derived)用于创建{ {1}}。由于some_method不在类语句的主体中,因此它不会出现在传递给dict的{​​{1}}对象中,从而导致Meta.__new__被引发。 (请注意,您应该提出AttributeError,而不是NotImplemented,因为您还没有尝试访问任何特定属性。)

答案 2 :(得分:1)

body包含您定义的类中定义的内容。在您的情况下,some_method中未定义Derived,但它并不存在。

当您使用Derived().some_method()(或甚至Derived.some_method)时,它会执行查找。它不会在Derived中找到它,但它会使用MRO(方法解析顺序)尝试父类,即基础。

请参阅def __new__(cls, name, bases, body):中的参数,在MRO启动之前,基数与您在班级中定义的body分开。