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

Python 元类:为什么方法不从基类继承?,python,python-3.x,metaclass,Python,Python 3.x,Metaclass,我试图理解Python中的元类黑魔法。例如,元类可以用来确保在派生类中实现某些方法,但我有一个问题。似乎我需要显式地实现所有必需的派生方法,即使没有理由(?)这样做 看看这个: ~ $ 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中的元类黑魔法。例如,元类可以用来确保在派生类中实现某些方法,但我有一个问题。似乎我需要显式地实现所有必需的派生方法,即使没有理由(?)这样做

看看这个:

~ $ 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

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

继承不是将定义注入类的东西。相反,如果属性没有在本地定义,它允许在立即类之外继续查找属性

在没有元类的情况下,调用
Defined()。某些方法()
大致如下:

  • 将创建已定义的
    的实例
  • 名为
    some\u method
    的属性将查找该实例的
    \u dict\u
    属性,但找不到该属性
  • 某些方法
    派生中查找,但未找到
  • 在基于
    的派生类中的类的
    属性中搜索一些方法
    ,从
    基开始
  • Base.\uuu dict\uuu['some\u method']
    被找到,并调用其值 将
    Derived
    的实例作为其隐式第一个参数

  • 对于您的元类,
    派生的
    是使用其基类使用的相同元类创建的,意思是
    元。
    (不是
    类型。
    )用于创建
    派生的
    。由于
    some\u方法
    不在class语句的主体中,因此它不会出现在传递给
    Meta.\uuu new\uuu
    dict
    对象中,从而引发
    AttributeError
    。(请注意,由于您尚未尝试访问任何特定属性,因此应提高
    未实现
    ,而不是
    属性错误

    正文
    包含您正在定义的类中定义的内容。在您的例子中,
    派生的
    中没有定义某个方法
    ,它不存在于其中

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


    查看
    def\uu new\uuu(cls,name,base,body)中的参数:
    ,在MRO开始之前,这些基与
    body
    是分开的,后者是您在类中定义的。

    您似乎在试图重新设计模块及其
    @abstractmethod
    装饰器。我不知道你为什么要这样做,但如果你这样做,你应该看看(这是从文档链接)

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

    abc
    的方法非常简单:在检查是否实现了所有抽象方法之前,它在每个
    上调用
    getattr
    。(模拟类上的
    getattr
    操作的最佳方法是调用
    getattr
    。)在那里找到的任何东西都不需要出现在主体中

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

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

    看起来你只是想重现已经解决的问题(并且解决得更好),所以…为什么不直接使用它们呢?即使你不想使用它,你也可以看看。请注意,它解决了“我如何做
    getattr
    所做的事情”的问题,只需在基底上调用
    getattr
    。@babygame0ver你在说什么
    Derived()
    确实创建了
    Derived
    类的一个实例。
    Meta
    似乎重复了
    ABCMeta
    这一事实有些离题,这就是为什么
    Derived
    似乎没有从其父类继承
    某个方法的原因。@chepner我认为这根本没有离题,因为这是一个非常可靠的提示,告诉你在哪里可以找到这个问题的好答案。我写了一个解释它的答案,但我认为stdlib作为这个问题的官方解决方案做这件事的事实远比互联网上的一些人(甚至我)说它有效的事实更有用和权威。你的示例代码不适用于grand grand children@λ用户父类已经构建好了。当您对它调用
    getattr
    时,您可以看到它的父属性,包括方法,以及它的“祖父母”,等等。@abarnert我不知道这个模块!这正是我想要实现的,谢谢。这解释了为什么非元类示例有效,但它只简要介绍了元类示例无效的原因,然后没有给出任何解决方法的提示,这就是OP(我认为)实际寻找的。也就是说,它非常好地解释了非元类示例(简洁,但在所有相关细节上仍然完整,并且在不相关的细节上没有太多欺骗),因此我认为这仍然值得作为一个答案。@chepner那么我应该怎么做才能使我的示例起作用呢?我应该实现
    派生。_unew_u()
    ?如何实现?不,您应该查看
    abc
    模块以了解它是如何实现的。也就是说,
    Meta.\uuuu new.\uuuu
    应该查看
    基中的每个类
    以及它们是否实现了
    某些方法
    ,如果
    某些方法
    未在本地定义。(这种检查需要是递归的,因为它们可能都没有定义某个方法
    if (not any(hasattr(base, 'some_method') for base in bases)
        and 'some_method' not in body):