元类中 __new__ 的行为(也在继承的上下文中)

upg*_*grd 8 python metaclass

好的,显然,__new__在元类中,当元类的实例(即类对象被实例化)时运行,因此__new__在元类中提供了一个钩子来在类定义时拦截事件(/运行的代码)(例如,验证/执行类属性的规则,例如如方法等)。

\n

元类中的许多在线示例__new__返回来自 的类型构造函数的实例__new__,这似乎有点问题,因为这会阻止__init__文档:“如果__new__()不返回 cls 的实例,则新的实例\xe2\x80\x99s__init__()方法将不会被调用”)。

\n

在修改元类中的返回值时,__new__我遇到了一些我不完全理解的奇怪情况,例如:

\n
class Meta(type):\n    \n    def __new__(self, name, bases, attrs):\n        print("Meta __new__ running!")\n        # return type(name, bases, attrs)                     # 1. \n        # return super().__new__(self, name, bases, attrs)    # 2.\n        # return super().__new__(name, bases, attrs)          # 3.  \n        # return super().__new__(type, name, bases, attrs)    # 4. \n        # return self(name, bases, attrs)                     # 5.\n\n    def __init__(self, *args, **kwargs):\n        print("Meta __init__ running!")\n        return super().__init__(*args, **kwargs)\n    \nclass Cls(metaclass=Meta):\n    pass\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  1. 这在示例中经常出现并且通常有效,但是会阻塞__init__
  2. \n
  3. 这有效并且__init__也会触发;但为什么要将 self 传递给 super() 调用呢?self/cls 不应该通过 super() 自动传递吗?
  4. \n
  5. 这引发了一个有点奇怪的错误,我无法真正理解:TypeError:type.__new__(X) : X is not a type object (str); X是什么?self 不应该自动传递吗?
  6. \n
  7. 3. 的错误启发我尝试使用 super() 调用的第一个参数,因此我尝试直接传递类型;这也挡住了__init__。这里发生了什么?
  8. \n
  9. 只是为了好玩而尝试;这会导致递归错误
  10. \n
\n

特别是情况 1. 和 2. 似乎对于从绑定到元类的类继承具有相当深远的影响:

\n
class LowerCaseEnforcer(type):\n    """ Allows only lower case names as class attributes! """\n\n    def __new__(self, name, bases, attrs): \n        for name in attrs:\n            if name.lower() != name:\n                raise TypeError(f"Inappropriate method name: {name}")\n            \n        # return type(name, bases, attrs)                    # 1.\n        # return super().__new__(self, name, bases, attrs)   # 2.\n\n    class Super(metaclass=LowerCaseEnforcer):\n        pass\n    \n    class Sub(Super):\n        \n        def some_method(self):\n            pass\n    \n        ## this will error in case 2 but not case 1\n        def Another_method(self):\n            pass\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  1. 预期行为:元类绑定到超类,但不绑定到子类
  2. \n
  3. 将超类/和/子类绑定到元类;?
  4. \n
\n

如果有人能慢慢地、友善地解释上面的例子到底发生了什么,我将不胜感激!:D

\n

jsb*_*eno 6

它比你得到的也更简单。

正如您所指出的,正确的做法是2上面的内容:

return super().__new__(self, name, bases, attrs)    # 2.
Run Code Online (Sandbox Code Playgroud)

是这样的:__new__是一个特殊的方法 - 尽管在某些文档中,甚至是官方文档的一部分,它被描述为 a classmethod,但事实并非如此:它作为一个对象,行为更像是静态方法 - 在某种意义上当调用时,Python 不会自动填充第一个参数MyClass.__new__()- 即,您必须调用MyClass.__new__(MyClass)它才能工作。(我退一步 - 该信息适用于所有类:元类和普通类)。

当您调用MyClass()创建新实例时,Python 将调用MyClass.__new__并插入该cls参数作为第一个参数。

对于元类,创建元类新实例的调用是由class语句及其类主体的执行触发的。同样,Python 将第一个参数填充到Metaclass.__new__,传递元类本身。

当您super().__new__从元类中调用时__new__,您处于与手动调用相同的情况:必须显式填充指定应应用__new__哪个类的参数。__new__

现在,让您感到困惑的是您正在将第一个参数写入__new__as self- 如果它是元类的实例(即普通类),那么这是正确的。实际上,该参数是对元类本身的引用。

文档没有告知元类的第一个参数的官方或推荐名称__new__,但通常它是一致的mcls, mcs, metaclass, metacls- 使其不同于 cls非元类方法的第一个参数的通常名称__new__。在元类中,“类” -cls是通过最终调用type.__new__(硬编码或使用super())创建的,它的返回是新生类(可以在__new__调用超类后在方法中进一步修改) ) - 返回后,对 的调用__init__正常进行。

因此,我将进一步评论尝试调用type(name, bases, namespace)而不是type.__new__(mcls, name, bases, namespace):第一种形式将只创建一个普通类,就好像根本没有使用元类一样 - (元类中__new__修改命名空间或基类的行,当然,有它们的效果。但是生成的类将具有type其元类,并且它的子类根本不会调用元类。(根据记录,它充当“类前装饰器” - 可以作用于类创建之前的参数,它甚至可以是一个普通的函数,而不是具有方法的类-毕竟__new__调用将创建新类)type

检查元类是否“绑定”到您的类的一个简单方法是使用type(MyClass)或检查其类型MyClass.__class__