Metaclasses:为什么方法没有继承自Baseclass?

Metaclasses: why method is not inherited from Base class?

我正在尝试了解 Python 中的 metaclass 黑魔法 。 AFAIK,例如,可以使用 metaclasses 来确保在派生 class 中实现某些方法,但我有一个问题 grandchildrens。似乎我需要显式实现 all 所需的派生方法,即使没有理由(?)这样做。

看看这个:

~ $ 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 class,如下所示:

In [3]: class Base:
   ...:     def some_method(self):
   ...:         print('Yay')
   ...: 
   ...: class Derived(Base):
   ...:     pass
   ...: 
   ...: 

In [4]: Derived().some_method()
Yay

这是预料之中的?如果是的话,你能给我一些提示如何修复这个行为,这样我就不需要重新实现方法了吗?

继承不是将定义注入 class 的东西。相反,它允许 lookup 属性在直接 class 之外继续进行,如果它没有在本地定义的话。

在没有你的元class的情况下,对Defined().some_method()的调用大致如下:

  1. 已创建 Defined 的实例。
  2. 为该实例的 __dict__ 属性查找名为 some_method 的属性,但未找到。
  3. some_methodDerived.__dict__ 中查找但未找到。
  4. some_methodDerived 的基础 classes 中的 classes 的 __dict__ 属性中搜索,从 [=19 开始=].
  5. 找到
  6. Base.__dict__['some_method'],调用它的值 将 Derived 的实例作为其隐含的第一个参数。

使用你的元class,Derived是用它的基础class使用的相同元class创建的,意思是 Meta.__new__ (不是 type.__new__)用于创建 Derived 。由于 some_method 不在 class 语句的主体中,因此它不会出现在传递给 Meta.__new__dict 对象中,导致引发 AttributeError . (请注意,您应该提高 NotImplemented,而不是 AttributeError,因为您还没有尝试访问任何特定属性。)

body 包含您正在定义的 class 中定义的内容。在您的情况下,some_method 未在 Derived 中定义,它不存在于其中。

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

查看 def __new__(cls, name, bases, body): 中的参数,在 MRO 启动之前,这些基数与 body 是分开的,这是您在 class 中定义的。

您似乎正在尝试重新发明 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 的轮子(从文档链接)。

您的解决方案会检查 class 的正文中是否实现了所需的名称。对于继承的方法,情况并非如此,但您似乎知道这一点,所以我不解释了。你想知道的是:你能做什么呢?您需要明确检查继承的方法。

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

您的用例——使用静态的单个必需方法而不是必须从装饰器的输出中获取的一组动态方法——甚至更简单:

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