在元类中重写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
会收到带有参数AHELLO
的{{1}}。
我以为name
调用了__init__
:如果未在覆盖的super().__new__
中进行调用,那么不会调用我的__new__
。
有人可以澄清在这种情况下如何调用__init__
吗?
作为参考,我的用例是我想通过仅提供一个“基”类(而不是元组)来简化类的创建,在特殊情况下,我在运行时将其添加到__init__
:
__new__
但是,我发现我还需要将其添加到if not isinstance(bases, tuple):
bases = (bases, )
中。
答案 0 :(得分:3)
您的__init__
方法显然被调用了,其原因是因为您的__new__
方法正在返回类的实例。
来自https://docs.python.org/3/reference/datamodel.html#object.new:
如果
__new__()
返回cls的实例,则将像__init__()
一样调用新实例的__init__(self[, ...])
方法,其中self是新实例,其余参数与传递给__new__()
。
如您所见,传递给__init__
的参数是传递给__new__
方法的调用者的参数,而不是使用super
调用时。这有点含糊,但是如果您仔细阅读,那就意味着什么。
关于其余部分,它按预期工作:
In [10]: A.__bases__
Out[10]: (list,)
In [11]: a = A()
In [12]: a.__class__.__bases__
Out[12]: (list,)
答案 1 :(得分:1)
事实是,协调普通类的__new__
和__init__
调用的是其元类上的__call__
方法。默认元类型__call__
的{{1}}方法中的代码在C中,但是在Python中等效于:
type
这在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
调用的__new__
和__init__
是元类本身的方法。在这种情况下,type.__call__
充当了“ metametaclass” –一个很少需要的概念,但这正是创建您正在探索的行为的原因。
创建类时,type
将负责调用该类(不是元类)type.__new__
及其描述符的__init_subclass__
方法-因此,“ metametaclass” {{1 }}方法无法控制。
因此,如果您希望以编程方式修改传递给元类__set_name__
的args,则“正常”方式将是拥有一个“元类”,该元类继承自__call__
并与您的元类不同本身,并覆盖其__init__
方法:
type
当然,这是比生产代码中任何人都更想要接近“乌龟到底部”的一种方法。
另一种方法是将修改后的名称保留为元类的属性,以便其__call__
方法可以从那里获取所需的信息,而忽略从其自己的元类'{{1}中传入的名称}调用。信息通道可以是元类实例上的普通属性。很好-碰巧“元类实例”是自己创建的类-哦,瞧瞧-传递给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)
的名称已经记录在__init__
属性中。
换句话说,您要使用在元类__call__
方法中修改的类名和自己的type.__new__
方法一起使用,要做的就是忽略传入的__name__
参数,并改用__new__
:
__init__