元类中 __new__ 方法的行为(也涉及继承)

8

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

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

当尝试使用元类中的__new__的返回值时,我遇到了一些奇怪的情况,其中有些我并不完全理解,例如:

class Meta(type):
    
    def __new__(self, name, bases, attrs):
        print("Meta __new__ running!")
        # return type(name, bases, attrs)                     # 1. 
        # return super().__new__(self, name, bases, attrs)    # 2.
        # return super().__new__(name, bases, attrs)          # 3.  
        # return super().__new__(type, name, bases, attrs)    # 4. 
        # return self(name, bases, attrs)                     # 5.

    def __init__(self, *args, **kwargs):
        print("Meta __init__ running!")
        return super().__init__(*args, **kwargs)
    
class Cls(metaclass=Meta):
    pass
  1. 这经常在例子中看到,通常可行,但会阻止__init__
  2. 这样可以工作,并且__init__也会触发;但是为什么要在super()调用中传递self?难道不应该自动通过super()传递self/cls吗?
  3. 这引发了一个我无法理解的奇怪错误:TypeError:type.__new__(X):X不是类型对象(str);这里的X是什么?难道self不应该自动传递吗?
  4. 3.的错误启发我尝试传递type直接作为super()调用的第一个参数;这也会阻止__init__。这里发生了什么?
  5. 仅出于好玩尝试;这导致了RecursionError。

特别是案例1和2似乎对继承绑定到元类的类有深远的影响:

class LowerCaseEnforcer(type):
    """ Allows only lower case names as class attributes! """

    def __new__(self, name, bases, attrs): 
        for name in attrs:
            if name.lower() != name:
                raise TypeError(f"Inappropriate method name: {name}")
            
        # return type(name, bases, attrs)                    # 1.
        # return super().__new__(self, name, bases, attrs)   # 2.

    class Super(metaclass=LowerCaseEnforcer):
        pass
    
    class Sub(Super):
        
        def some_method(self):
            pass
    
        ## this will error in case 2 but not case 1
        def Another_method(self):
            pass
  1. 预期行为:元类绑定到超类,但不绑定到子类
  2. 将超类和子类绑定到元类;?

如果有人能缓慢、友善地解释上述示例中正在发生的事情,我会非常感激!:D

2个回答

6

这很简单,甚至比你得到的更简单。

正如您所注意到的,正确的做法是您上面的 2

return super().__new__(self, name, bases, attrs)    # 2.

下面开始翻译:__new__是一个特殊的方法 - 尽管在某些文档中,甚至是官方文档的一部分中,它被描述为一个classmethod,但它并不完全是这样:它作为一个对象,更像一个静态方法 - 在Python调用MyClass.__new__()时,Python不会自动填充第一个参数,也就是说,你必须调用MyClass.__new__(MyClass)才能让它工作。 (我在这里退了一步 - 这些信息适用于所有类:元类和普通类)。
当你调用MyClass()来创建一个新实例时,Python将调用MyClass.__new__并将cls参数插入为第一个参数。
对于元类,创建元类的新实例的调用是由执行class语句及其类体触发的。同样,Python会将第一个参数传递给Metaclass.__new__,传递元类本身。
在元类的__new__中从super().__new__调用时,您与手动调用__new__的情况相同:指定应用该__new__的类的参数必须明确填充。
现在,让您感到困惑的是,您将__new__的第一个参数写为self - 如果它是元类的实例(即普通类),那么这是正确的。由于它是,该参数是对元类本身的引用。
文档没有提供元类__new__的第一个参数的官方或推荐名称,但通常是mcls、mcs、metaclass、metacls之类的东西 - 以使其与非元类__new__方法的第一个参数的通常名称cls不同。 在元类中,“类” - cls是通过最终调用type.__new__(硬编码或使用super())创建的,它的返回值是新生的类(可以在调用超类后在__new__方法中进一步修改)- 当返回时,调用__init__会正常进行。
因此,我将进一步评论尝试调用type(name, bases, namespace)而不是type.__new__(mcls, name, bases, namespace)的用途:第一种形式将仅创建一个普通类,就好像根本没有使用元类 - (当然,修改命名空间或基类的元类__new__中的行会产生影响。但是,生成的类将具有type作为其元类,并且其子类根本不会调用元类。(记录一下,它可以作为“预类装饰器”工作-在创建类之前可以对类参数进行操作,甚至可以是具有__new__方法的类的普通函数-调用type将创建新类)

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


谢谢您的回答!我终于弄清了问题所在,并在这里做了总结:https://dev59.com/U8Dqa4cB1Zd3GeqPak_T#67297367 - upgrd

3

我想我终于有点明白了,我的初始困惑主要是因为我没有意识到以下几点:

  1. object.__new__type.__new__ 之间有区别
  2. 从元类返回 type() 和返回 super().__new__ 有所不同

对这两个观点的讨论应该能够澄清我的初始示例以及看似神秘的继承行为。

1. object.__new__type.__new__ 的区别

首先,关于 __new__ 的一些话。文档 对此非常清楚,但我仍然想添加和/或强调一些事情:

  • __new__ 可以被理解为一个特殊的静态方法,它将 cls 作为第一个参数,并将其余参数(通常是 *args, **kwargs)传递给 __init__
  • __new__ 接受单个 参数(这是关于调用 __new__,而不是定义/重载它),即 cls,并返回该类的实例。

一件我最初忽略的重要事情是,object.__new__type.__new__ 之间存在差异。 当我在尝试使用 __new__ 的参数时发现了这一点;看看这些“有教育意义”的错误:

class ObjNewExample:
    
    def __new__(cls, *args, **kwargs):
        # return super().__new__(cls)                      # correct 
        return super().__new__(cls, *args, **kwargs)       # instructive error

    def __init__(self, some_attr):
        self._some_attr = some_attr

one = ObjNewExample(42)

class TypeNewExample(type):
    
    def __new__(mcls, name, bases, attrs):
        # return super().__new__(mcls, name, bases, attrs)  # correct
        return super().__new__(mcls)                        # instructive error

# class Cls(metaclass=TypeNewExample):
#     pass

使用return super().__new__(cls, *args, **kwargs)的ObjNewexample会抛出类似以下错误:

  • TypeError: object.__new__() takes exactly one argument (the type to instantiate),而使用return super().__new__(mcls)的TypeNewexample则会抛出
  • TypeError: type.__new__() takes exactly 3 arguments,这表明object.__new__type.__new__是非常不同的方法!

另外:请考虑使用__new__时参数和参数值之间的区别:

  • object.__new__cls、*args、**kwargs作为参数,但只需要cls作为参数值(*args、**kwargs会传递给__init__
  • type.__new__mcls、name、bases、attrs作为参数和参数值

从元类返回type()super().__new__之间的区别

我最初发布的示例的主要问题在于返回元类的__new__中的type()super().__new__之间的差异(现在非常明显..)。(请参见this discussion

  • mcls中返回type(name, bases, attrs):创建type的实例
  • mcls中返回super().__new__(mcls, name, bases, attrs):创建实际元类的实例(派生自type),这也解释了为什么最初示例的情况1中抑制了__init__但情况2中没有! (记住:__init__如果__new__返回的不是( m)cls 的实例,则不会调用__new__

这应该是有教育意义的:

class Meta(type):
    def __new__(mcls, name, bases, attrs):
        
        # this creates an intance of type (the default metaclass)
        # This should be eql to super().__new__(type, name, base, attrs)!
        obj1 = type(name, bases, attrs) 
        
        # this creates an instance of the actual custom metaclass (which is derived from type)
        # i.e. it creates an instance of type.__new__'s first arg
        obj2 = super().__new__(mcls, name, bases, attrs)

        print(isinstance(obj1, mcls))
        print(obj1.__class__)
        print(isinstance(obj2, mcls))
        print(obj2.__class__)
        
class Fun(metaclass=Meta):
    pass

快速浏览一下我最初的帖子中的1-5个案例:

1:返回一个新的type对象,它是type的实例,而不是实际的自定义元类(派生自type),因此抑制了自定义元类的__init__;这似乎实际上等同于案例4!

2:正如@jsbueno指出的那样,这是最有可能预期的(“正确”的)行为:这将创建实际自定义元类的实例。

3:这会导致错误,因为type.__new__期望第一个参数为type类型的对象(要实例化的对象)

4:参见案例1

5:self(可能更好地命名为'cls'或'mcls')是Meta;在其自己的构造函数中调用类显然是递归的。

以上还解释了我最初的帖子中第二个片段看似奇怪的继承行为的原因! 那么为什么在LowerCaseEnforcer的情况2中,SubAnother_method的定义会出错,但在情况1中不会呢?

因为在情况1中,Lowercaseenforcer返回的是一个type实例(而不是LowerCaseEnforcer的实例!),所以Supertype类型(其元类是type,而不是LowerCaseEnforcer)!因此,虽然LowerCaseEnforcer.__new__触发并强制执行Super的小写限制,但Super只是一个普通的type类,而Sub是从它派生出来的(没有特殊效果)。

而在情况2中,Super的元类是LowerCaseEnforcerSub的元类也是如此,因此LowerCaseEnforcer.__new__参与了Sub的定义。

然而仍然有一件事情不太清楚,就是超类调用中静态方法的行为(参见this discussion)。 例如,为什么super().__new__(cls)可以工作?这不应该是super(cls, cls).__new__(cls)(或类似的代码)吗? 但我猜这是另一个(有趣的)话题!:D


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接