gpt4 book ai didi

python - 为什么类定义的元类关键字参数接受可调用对象?

转载 作者:太空狗 更新时间:2023-10-29 20:51:14 24 4
gpt4 key购买 nike

背景

Python 3 documentation清楚地描述了一个类的元类是如何确定的:

  • if no bases and no explicit metaclass are given, then type() is used
  • if an explicit metaclass is given and it is not an instance of type(), then it is used directly as the metaclass
  • if an instance of type() is given as the explicit metaclass, or bases are defined, then the most derived metaclass is used

因此,根据第二条规则,可以使用可调用对象指定元类。例如,

class MyMetaclass(type):
pass

def metaclass_callable(name, bases, namespace):
print("Called with", name)
return MyMetaclass(name, bases, namespace)

class MyClass(metaclass=metaclass_callable):
pass

class MyDerived(MyClass):
pass

print(type(MyClass), type(MyDerived))

问题一

MyClass的元类是metaclass_callable还是MyMetaclass?文档中的第二条规则说提供的可调用“直接用作元类”。但是,说元类是 MyMetaclass 似乎更有意义,因为

  • MyClassMyDerived 具有类型 MyMetaclass,
  • metaclass_callable 被调用一次,然后似乎无法恢复,
  • 派生类不以任何方式使用(据我所知)metaclass_callable(它们使用 MyMetaclass)。

问题2

type 的实例不能用可调用对象做哪些事?接受任意可调用对象的目的是什么?

最佳答案

关于你的第一个问题,元类应该是MyMetaclass(它是这样的):

In [7]: print(type(MyClass), type(MyDerived))
<class '__main__.MyMetaclass'> <class '__main__.MyMetaclass'>

原因是,如果元类不是类型的实例,python 通过将这些参数传递给它来调用元类 name, bases, ns, **kwds(参见 new_class) 并且由于您在该函数中返回了真正的元类,因此它获得了元类的正确类型。

关于第二个问题:

What is the purpose of accepting an arbitrary callable?

没有特殊用途,实际上是元类的本质,因为从类创建实例总是通过调用它的 __call__ 方法来调用元类:

Metaclass.__call__()

这意味着您可以将任何可调用对象作为您的元类传递。因此,例如,如果您使用嵌套函数对其进行测试,结果仍然是相同的:

In [21]: def metaclass_callable(name, bases, namespace):
def inner():
return MyMetaclass(name, bases, namespace)
return inner()
....:

In [22]: class MyClass(metaclass=metaclass_callable):
pass
....:

In [23]: print(type(MyClass), type(MyDerived))
<class '__main__.MyMetaclass'> <class '__main__.MyMetaclass'>

有关更多信息,请参见 Python 如何创建类:

它调用 new_class 函数,它在自身内部调用 prepare_class,然后正如您在 prepare_class 中看到的,python 调用 __prepare__ 适当元类的方法,除了找到适当的元(使用 _calculate_meta 函数)并为类创建适当的命名空间。

所以这里的所有内容都是执行 metacalss 方法的层次结构:

  1. __prepare__ 1
  2. __call__
  3. __new__
  4. __init__

这里是源代码:

# Provide a PEP 3115 compliant mechanism for class creation
def new_class(name, bases=(), kwds=None, exec_body=None):
"""Create a class object dynamically using the appropriate metaclass."""
meta, ns, kwds = prepare_class(name, bases, kwds)
if exec_body is not None:
exec_body(ns)
return meta(name, bases, ns, **kwds)

def prepare_class(name, bases=(), kwds=None):
"""Call the __prepare__ method of the appropriate metaclass.

Returns (metaclass, namespace, kwds) as a 3-tuple

*metaclass* is the appropriate metaclass
*namespace* is the prepared class namespace
*kwds* is an updated copy of the passed in kwds argument with any
'metaclass' entry removed. If no kwds argument is passed in, this will
be an empty dict.
"""
if kwds is None:
kwds = {}
else:
kwds = dict(kwds) # Don't alter the provided mapping
if 'metaclass' in kwds:
meta = kwds.pop('metaclass')
else:
if bases:
meta = type(bases[0])
else:
meta = type
if isinstance(meta, type):
# when meta is a type, we first determine the most-derived metaclass
# instead of invoking the initial candidate directly
meta = _calculate_meta(meta, bases)
if hasattr(meta, '__prepare__'):
ns = meta.__prepare__(name, bases, **kwds)
else:
ns = {}
return meta, ns, kwds


def _calculate_meta(meta, bases):
"""Calculate the most derived metaclass."""
winner = meta
for base in bases:
base_meta = type(base)
if issubclass(winner, base_meta):
continue
if issubclass(base_meta, winner):
winner = base_meta
continue
# else:
raise TypeError("metaclass conflict: "
"the metaclass of a derived class "
"must be a (non-strict) subclass "
"of the metaclasses of all its bases")
return winner

<子>1. 请注意,它在 new_class 函数内部和返回之前被隐式调用。

关于python - 为什么类定义的元类关键字参数接受可调用对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40029807/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com