元类的 __new__ 和 __init__ 参数
Arguments of __new__ and __init__ for metaclasses
在 metaclass 中覆盖 new
和 init
时,我对方法调用顺序和不同的参数感到有点惊讶。考虑以下因素:
class AT(type):
def __new__(mcs, name, bases, dct):
print(f"Name as received in new: {name}")
return super().__new__(mcs, name + 'HELLO', bases + (list,), dct)
def __init__(cls, name, bases, dct):
print(f"Name as received in init: {name}")
pass
class A(metaclass=AT):
pass
A.__name__
输出为:
Name as received in new: A
Name as received in init: A
'AHELLO'
简而言之,我本以为 init
会收到带有参数 name
的 AHELLO
。
我想象 __init__
被 super().__new__
调用:如果调用没有在覆盖的 __new__
中完成,那么我的 __init__
就不会被调用。
谁能解释一下 __init__
在这种情况下是如何调用的?
有关我的用例的信息,我想创建 classes,在特殊情况下,通过仅提供一个 "base" class 在运行时更容易(而不是元组),然后我在 __new__
:
中添加了这段代码
if not isinstance(bases, tuple):
bases = (bases, )
然而,我发现我还需要在__init__
中添加它。
你的 __init__
方法显然被调用了,原因是你的 __new__
方法返回了你的 class.
的实例
来自 https://docs.python.org/3/reference/datamodel.html#object.new:
If __new__()
returns an instance of cls, then the new instance’s __init__()
method will be invoked like __init__(self[, ...])
, where self is the new instance and the remaining arguments are the same as were passed to __new__()
.
如您所见,传递给 __init__
的参数是传递给 __new__
方法调用者的参数,而不是当您使用 super
调用它时。有点模糊,但仔细阅读就是这个意思。
关于其余部分,它按预期工作:
In [10]: A.__bases__
Out[10]: (list,)
In [11]: a = A()
In [12]: a.__class__.__bases__
Out[12]: (list,)
事实是,协调调用普通 class 的 __new__
和 __init__
的是其元 class 上的 __call__
方法。默认元类型 type
的 __call__
方法中的代码是用 C 编写的,但在 Python 中的等效代码是:
class type:
...
def __call__(cls, *args, **kw):
instance = cls.__new__(cls, *args, **kw) # __new__ is actually a static method - cls has to be passed explicitly
if isinstance(instance, cls):
instance.__init__(*args, **kw)
return instance
Python 中的大多数对象实例化都会发生这种情况,包括实例化 classes 本身时 - metaclass 作为 class 语句的一部分被隐式调用.在这种情况下,从 type.__call__
调用的 __new__
和 __init__
是 metaclass 本身的方法。在这种情况下,type
充当 "metametaclass" - 一个很少需要的概念,但它是创建您正在探索的行为的原因。
创建 classes 时,type.__new__
将负责调用 class(不是元class)__init_subclass__
,及其描述符__set_name__
方法 - 因此,"metametaclass" __call__
方法无法控制。
因此,如果您希望以编程方式修改传递给 metaclass __init__
的参数,"normal" 方法将有一个 "metametaclass",继承来自 type
并与您的元 class 本身不同,并覆盖其 __call__
方法:
class MM(type):
def __call__(metacls, name, bases, namespace, **kw):
name = modify(name)
cls = metacls.__new__(metacls, name, bases, namespace, **kw)
metacls.__init__(cls, name, bases, namespace, **kw)
return cls
# or you could delegate to type.__call__, replacing the above with just
# return super().__call__(modify(name), bases, namespace, **kw)
当然,这是一种比生产代码更接近 "turtles all way to the bottom" 的方法。
另一种方法是将修改后的名称保留为元class 上的一个属性,以便其 __init__
方法可以从那里获取所需的信息,并忽略从其传入的名称自己的 metaclass' __call__
调用。信息通道可以是 metaclass 实例上的一个普通属性。好吧 - 碰巧 "metaclass instance" 是 class 自己创建的 - 哦,看 - 传递给 type.__new__
的名称已经记录在其中 - 在 __name__
属性.
换句话说,要在 metaclass __new__
方法中使用修改过的 class 名称,您需要做的就是在它自己的 __init__
方法中使用忽略传入的 name
参数,并使用 cls.__name__
代替:
class Meta(type):
def __new__(mcls, name, bases, namespace, **kw):
name = modified(name)
return super().__new__(mcls, name, bases, namespace, **kw)
def __init__(cls, name, bases, namespace, **kw):
name = cls.__name__ # noQA (otherwise linting tools would warn on the overriden parameter name)
...
在 metaclass 中覆盖 new
和 init
时,我对方法调用顺序和不同的参数感到有点惊讶。考虑以下因素:
class AT(type):
def __new__(mcs, name, bases, dct):
print(f"Name as received in new: {name}")
return super().__new__(mcs, name + 'HELLO', bases + (list,), dct)
def __init__(cls, name, bases, dct):
print(f"Name as received in init: {name}")
pass
class A(metaclass=AT):
pass
A.__name__
输出为:
Name as received in new: A
Name as received in init: A
'AHELLO'
简而言之,我本以为 init
会收到带有参数 name
的 AHELLO
。
我想象 __init__
被 super().__new__
调用:如果调用没有在覆盖的 __new__
中完成,那么我的 __init__
就不会被调用。
谁能解释一下 __init__
在这种情况下是如何调用的?
有关我的用例的信息,我想创建 classes,在特殊情况下,通过仅提供一个 "base" class 在运行时更容易(而不是元组),然后我在 __new__
:
if not isinstance(bases, tuple):
bases = (bases, )
然而,我发现我还需要在__init__
中添加它。
你的 __init__
方法显然被调用了,原因是你的 __new__
方法返回了你的 class.
来自 https://docs.python.org/3/reference/datamodel.html#object.new:
If
__new__()
returns an instance of cls, then the new instance’s__init__()
method will be invoked like__init__(self[, ...])
, where self is the new instance and the remaining arguments are the same as were passed to__new__()
.
如您所见,传递给 __init__
的参数是传递给 __new__
方法调用者的参数,而不是当您使用 super
调用它时。有点模糊,但仔细阅读就是这个意思。
关于其余部分,它按预期工作:
In [10]: A.__bases__
Out[10]: (list,)
In [11]: a = A()
In [12]: a.__class__.__bases__
Out[12]: (list,)
事实是,协调调用普通 class 的 __new__
和 __init__
的是其元 class 上的 __call__
方法。默认元类型 type
的 __call__
方法中的代码是用 C 编写的,但在 Python 中的等效代码是:
class type:
...
def __call__(cls, *args, **kw):
instance = cls.__new__(cls, *args, **kw) # __new__ is actually a static method - cls has to be passed explicitly
if isinstance(instance, cls):
instance.__init__(*args, **kw)
return instance
Python 中的大多数对象实例化都会发生这种情况,包括实例化 classes 本身时 - metaclass 作为 class 语句的一部分被隐式调用.在这种情况下,从 type.__call__
调用的 __new__
和 __init__
是 metaclass 本身的方法。在这种情况下,type
充当 "metametaclass" - 一个很少需要的概念,但它是创建您正在探索的行为的原因。
创建 classes 时,type.__new__
将负责调用 class(不是元class)__init_subclass__
,及其描述符__set_name__
方法 - 因此,"metametaclass" __call__
方法无法控制。
因此,如果您希望以编程方式修改传递给 metaclass __init__
的参数,"normal" 方法将有一个 "metametaclass",继承来自 type
并与您的元 class 本身不同,并覆盖其 __call__
方法:
class MM(type):
def __call__(metacls, name, bases, namespace, **kw):
name = modify(name)
cls = metacls.__new__(metacls, name, bases, namespace, **kw)
metacls.__init__(cls, name, bases, namespace, **kw)
return cls
# or you could delegate to type.__call__, replacing the above with just
# return super().__call__(modify(name), bases, namespace, **kw)
当然,这是一种比生产代码更接近 "turtles all way to the bottom" 的方法。
另一种方法是将修改后的名称保留为元class 上的一个属性,以便其 __init__
方法可以从那里获取所需的信息,并忽略从其传入的名称自己的 metaclass' __call__
调用。信息通道可以是 metaclass 实例上的一个普通属性。好吧 - 碰巧 "metaclass instance" 是 class 自己创建的 - 哦,看 - 传递给 type.__new__
的名称已经记录在其中 - 在 __name__
属性.
换句话说,要在 metaclass __new__
方法中使用修改过的 class 名称,您需要做的就是在它自己的 __init__
方法中使用忽略传入的 name
参数,并使用 cls.__name__
代替:
class Meta(type):
def __new__(mcls, name, bases, namespace, **kw):
name = modified(name)
return super().__new__(mcls, name, bases, namespace, **kw)
def __init__(cls, name, bases, namespace, **kw):
name = cls.__name__ # noQA (otherwise linting tools would warn on the overriden parameter name)
...