Ced*_* H. 5 python metaclass python-3.x
我对方法调用顺序和覆盖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__
Run Code Online (Sandbox Code Playgroud)
输出是:
Name as received in new: A
Name as received in init: A
'AHELLO'
Run Code Online (Sandbox Code Playgroud)
简而言之,我本来预计init会收到AHELLO争论name。
我想象这__init__是由super().__new__以下调用的:如果未在覆盖中完成调用,则不会调用__new__my __init__。
有人可以澄清__init__在这种情况下如何调用吗?
有关信息,我的用例是我想在特殊情况下通过仅提供一个“基”类(而不是元组)来在运行时更轻松地创建类,然后我添加了以下代码__new__:
if not isinstance(bases, tuple):
bases = (bases, )
Run Code Online (Sandbox Code Playgroud)
但是,我发现我还需要将它添加到__init__.
事实上,协调普通类的调用的是__new__其元类上的方法。默认元类型的方法中的代码是用 C 编写的,但在 Python 中等效的代码是:__init____call____call__type
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
Run Code Online (Sandbox Code Playgroud)
Python 中的大多数对象实例化都会发生这种情况,包括实例化类本身时 - 元类作为类语句的一部分隐式调用。在这种情况下,__new__和__init__调用自是元类type.__call__本身的方法。在这种情况下,它充当“元元类”——一个很少需要的概念,但它创造了您正在探索的行为。type
创建类时,type.__new__将负责调用类(而不是元类)__init_subclass__及其描述符的__set_name__方法 - 因此,“元元类”__call__方法无法控制这一点。
因此,如果您希望以编程方式修改传递给元类的参数__init__,“正常”方法将是拥有一个“元元类”,继承type并不同于您的元类本身,并重写其__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)
Run Code Online (Sandbox Code Playgroud)
当然,这是一种比任何人在生产代码中所希望的更接近“海龟一直到底部”的方法。
另一种方法是将修改后的名称保留为元类的属性,以便其__init__方法可以从那里获取所需的信息,并忽略从其自己的元类调用中传入的名称__call__。信息通道可以是元类实例上的普通属性。好吧 - 碰巧“元类实例”是正在创建的类本身 - 哦,看 - 传递给的名称type.__new__已经记录在其中 - 在__name__属性上。
换句话说,要__new__在其自己的__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)
...
Run Code Online (Sandbox Code Playgroud)
显然调用了您的__init__方法,其原因是您的__new__方法返回了类的实例。
来自https://docs.python.org/3/reference/datamodel.html#object。新的:
\n\n\n\n\n如果
\n__new__()返回 cls 的实例,则将__init__()像 一样调用新的 instance\xe2\x80\x99s 方法__init__(self[, ...]),其中 self 是新实例,其余参数与传递给 的参数相同__new__()。
正如您所看到的,传递给方法的参数__init__是传递给__new__方法的调用者的参数,而不是当您使用super. 虽然有点含糊,但是如果你仔细阅读的话就是这个意思。
至于其余部分,它的工作原理与预期一致:
\n\nIn [10]: A.__bases__\nOut[10]: (list,)\n\nIn [11]: a = A()\n\nIn [12]: a.__class__.__bases__\nOut[12]: (list,)\nRun Code Online (Sandbox Code Playgroud)\n
| 归档时间: |
|
| 查看次数: |
3199 次 |
| 最近记录: |