为什么 class 定义的 metaclass 关键字参数接受一个可调用对象?
Why does the class definition's metaclass keyword argument accept a callable?
背景
Python 3 documentation 清楚地描述了 class 的 metaclass 是如何确定的:
- 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。例如,
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))
问题 1
MyClass
的 metaclass 是:metaclass_callable
还是 MyMetaclass
?文档中的第二条规则说提供的可调用 "is used directly as the metaclass"。但是,说 metaclass 是 MyMetaclass
似乎更有意义,因为
MyClass
和 MyDerived
的类型为 MyMetaclass
,
metaclass_callable
被调用一次,然后似乎无法恢复,
- 派生的 classes 不以任何方式使用(据我所知)
metaclass_callable
(他们使用 MyMetaclass
)。
问题 2
有什么可以用可调用对象做但不能用 type
实例做的事情吗?接受任意可调用对象的目的是什么?
嗯,type
当然是MyMetaClass
。 metaclass_callable
最初是 'selected' 作为 metaclass 因为 it's been specified in the metaclass
kwarg 因此,它将被执行 __call__
(一个简单的函数调用)。
碰巧调用它会 print
然后调用 MyMetaClass.__call__
(调用 type.__call__
因为 __call__
没有被覆盖 MyMetaClass
). There the assignment of cls.__class__
is made 到 MyMetaClass
。
metaclass_callable
is called once and then appears to be unrecoverable
是的,它仅在最初被调用,然后将控制权移交给 MyMetaClass
。我不知道有任何 class 属性可以保留该信息。
derived classes do not use (as far as I can tell) metaclass_callable
in any way.
不,如果没有明确定义metaclass
,将使用the best match for the metaclasses of bases
(此处MyClass
)(结果为MyMetaClass
)。
至于问题 2
,可以肯定的是,通过使用相应覆盖 __call__
的类型实例,您也可以使用可调用对象执行的所有操作。至于 为什么 ,如果你只是想在实际创建一个 class.[=36 时做一些小的改变,你可能不想完全成熟 class-creation =]
关于你的第一个问题,metaclass 应该是 MyMetaclass
(确实如此):
In [7]: print(type(MyClass), type(MyDerived))
<class '__main__.MyMetaclass'> <class '__main__.MyMetaclass'>
原因是,如果 metaclass 不是类型 python 的实例,则通过将这些参数传递给它来调用 methaclass name, bases, ns, **kwds
(请参阅 new_class
) 并且由于您在该函数中 returning 了您的真实元 class 它获得了元 class.
的正确类型
关于第二个问题:
What is the purpose of accepting an arbitrary callable?
没有特殊用途,实际上是 metaclasses 的本质,因为从 class 创建实例总是调用metaclass 通过调用它的 __call__
方法:
Metaclass.__call__()
这意味着您可以将任何可调用对象作为您的元class传递。因此,例如,如果您使用嵌套函数对其进行测试,结果仍然是相同的:
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 如何创建 class:
它调用 new_class
函数,它在自身内部调用 prepare_class
,然后正如您在 prepare_class
中看到的,python 调用 __prepare__
方法适当的元 class,除了找到适当的元(使用 _calculate_meta
函数)并为 class.
创建适当的命名空间
所以这里的所有内容都是执行 metacalss 方法的层次结构:
__prepare__
1
__call__
__new__
__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 函数内部和 return 之前隐式调用。
关于问题1,我觉得classcls
的"metaclass"应该理解为type(cls)
。这种理解方式与以下示例中 Python 的错误消息兼容:
>>> class Meta1(type): pass
...
>>> class Meta2(type): pass
...
>>> def metafunc(name, bases, methods):
... if methods.get('version') == 1:
... return Meta1(name, bases, methods)
... return Meta2(name, bases, methods)
...
>>> class C1:
... __metaclass__ = metafunc
... version = 1
...
>>> class C2:
... __metaclass__ = metafunc
... version = 2
...
>>> type(C1)
<class '__main__.Meta1'>
>>> type(C2)
<class '__main__.Meta2'>
>>> class C3(C1,C2): pass
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Error when calling the metaclass bases
metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
即,根据错误消息,class 的 metaclass 是 class,即使用于构造 class 的可调用对象可以是随便什么。
关于第二个问题,确实将类型的子class用作元class,您可以像处理任何其他可调用函数一样执行相同的操作。特别是,它可能会产生一些不是它的实例的东西:
>>> class Mockup(type):
... def __new__(cls, name, bases, methods):
... return Meta1(name, bases, methods)
...
>>> class Foo:
... __metaclass__ = Mockup
...
>>> type(Foo)
<class '__main__.Meta1'>
>>> isinstance(Foo, Mockup)
False
>>> Foo.__metaclass__
<class '__main__.Mockup'>
至于为什么Python可以自由使用任何可调用对象:前面的例子说明可调用对象是否是类型实际上是无关紧要的。
顺便说一句,这里有一个有趣的例子:可以编写 metaclasses,它们本身有一个不同于 type
的 metaclass---我们称它为元元class。 metametaclass 实现调用 metaclass 时发生的事情。以这种方式,可以创建一个 class,其中两个碱基的 metaclasses 是 not subclass 彼此(与Python 上例中的错误信息!)。事实上,只有结果 class 的 metaclass 是基元 metaclass 的 subclass,并且这个 metaclass 是动态创建的:
>>> class MetaMeta(type):
... def __call__(mcls, name, bases, methods):
... metabases = set(type(X) for X in bases)
... metabases.add(mcls)
... if len(metabases) > 1:
... mcls = type(''.join([X.__name__ for X in metabases]), tuple(metabases), {})
... return mcls.__new__(mcls, name, bases, methods)
...
>>> class Meta1(type):
... __metaclass__ = MetaMeta
...
>>> class Meta2(type):
... __metaclass__ = MetaMeta
...
>>> class C1:
... __metaclass__ = Meta1
...
>>> class C2:
... __metaclass__ = Meta2
...
>>> type(C1)
<class '__main__.Meta1'>
>>> type(C2)
<class '__main__.Meta2'>
>>> class C3(C1,C2): pass
...
>>> type(C3)
<class '__main__.Meta1Meta2'>
不太有趣的地方:前面的例子在 Python 中不起作用 3. 如果我理解正确,Python 2 创建 class 并检查其元class 是其所有碱基的 subclass,而 Python 3 first 检查是否有一个碱基的 metaclass 是 superclass 所有其他基础的 metaclasses,只有 then 创建新的 class。从我的角度来看,这是一种倒退。但这应该是我即将 post...
的一个新问题的主题
编辑: 新问题是here
背景
Python 3 documentation 清楚地描述了 class 的 metaclass 是如何确定的:
- 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。例如,
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))
问题 1
MyClass
的 metaclass 是:metaclass_callable
还是 MyMetaclass
?文档中的第二条规则说提供的可调用 "is used directly as the metaclass"。但是,说 metaclass 是 MyMetaclass
似乎更有意义,因为
MyClass
和MyDerived
的类型为MyMetaclass
,metaclass_callable
被调用一次,然后似乎无法恢复,- 派生的 classes 不以任何方式使用(据我所知)
metaclass_callable
(他们使用MyMetaclass
)。
问题 2
有什么可以用可调用对象做但不能用 type
实例做的事情吗?接受任意可调用对象的目的是什么?
嗯,type
当然是MyMetaClass
。 metaclass_callable
最初是 'selected' 作为 metaclass 因为 it's been specified in the metaclass
kwarg 因此,它将被执行 __call__
(一个简单的函数调用)。
碰巧调用它会 print
然后调用 MyMetaClass.__call__
(调用 type.__call__
因为 __call__
没有被覆盖 MyMetaClass
). There the assignment of cls.__class__
is made 到 MyMetaClass
。
metaclass_callable
is called once and then appears to be unrecoverable
是的,它仅在最初被调用,然后将控制权移交给 MyMetaClass
。我不知道有任何 class 属性可以保留该信息。
derived classes do not use (as far as I can tell)
metaclass_callable
in any way.
不,如果没有明确定义metaclass
,将使用the best match for the metaclasses of bases
(此处MyClass
)(结果为MyMetaClass
)。
至于问题 2
,可以肯定的是,通过使用相应覆盖 __call__
的类型实例,您也可以使用可调用对象执行的所有操作。至于 为什么 ,如果你只是想在实际创建一个 class.[=36 时做一些小的改变,你可能不想完全成熟 class-creation =]
关于你的第一个问题,metaclass 应该是 MyMetaclass
(确实如此):
In [7]: print(type(MyClass), type(MyDerived))
<class '__main__.MyMetaclass'> <class '__main__.MyMetaclass'>
原因是,如果 metaclass 不是类型 python 的实例,则通过将这些参数传递给它来调用 methaclass name, bases, ns, **kwds
(请参阅 new_class
) 并且由于您在该函数中 returning 了您的真实元 class 它获得了元 class.
关于第二个问题:
What is the purpose of accepting an arbitrary callable?
没有特殊用途,实际上是 metaclasses 的本质,因为从 class 创建实例总是调用metaclass 通过调用它的 __call__
方法:
Metaclass.__call__()
这意味着您可以将任何可调用对象作为您的元class传递。因此,例如,如果您使用嵌套函数对其进行测试,结果仍然是相同的:
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 如何创建 class:
它调用 new_class
函数,它在自身内部调用 prepare_class
,然后正如您在 prepare_class
中看到的,python 调用 __prepare__
方法适当的元 class,除了找到适当的元(使用 _calculate_meta
函数)并为 class.
所以这里的所有内容都是执行 metacalss 方法的层次结构:
__prepare__
1__call__
__new__
__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 函数内部和 return 之前隐式调用。
关于问题1,我觉得classcls
的"metaclass"应该理解为type(cls)
。这种理解方式与以下示例中 Python 的错误消息兼容:
>>> class Meta1(type): pass
...
>>> class Meta2(type): pass
...
>>> def metafunc(name, bases, methods):
... if methods.get('version') == 1:
... return Meta1(name, bases, methods)
... return Meta2(name, bases, methods)
...
>>> class C1:
... __metaclass__ = metafunc
... version = 1
...
>>> class C2:
... __metaclass__ = metafunc
... version = 2
...
>>> type(C1)
<class '__main__.Meta1'>
>>> type(C2)
<class '__main__.Meta2'>
>>> class C3(C1,C2): pass
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Error when calling the metaclass bases
metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
即,根据错误消息,class 的 metaclass 是 class,即使用于构造 class 的可调用对象可以是随便什么。
关于第二个问题,确实将类型的子class用作元class,您可以像处理任何其他可调用函数一样执行相同的操作。特别是,它可能会产生一些不是它的实例的东西:
>>> class Mockup(type):
... def __new__(cls, name, bases, methods):
... return Meta1(name, bases, methods)
...
>>> class Foo:
... __metaclass__ = Mockup
...
>>> type(Foo)
<class '__main__.Meta1'>
>>> isinstance(Foo, Mockup)
False
>>> Foo.__metaclass__
<class '__main__.Mockup'>
至于为什么Python可以自由使用任何可调用对象:前面的例子说明可调用对象是否是类型实际上是无关紧要的。
顺便说一句,这里有一个有趣的例子:可以编写 metaclasses,它们本身有一个不同于 type
的 metaclass---我们称它为元元class。 metametaclass 实现调用 metaclass 时发生的事情。以这种方式,可以创建一个 class,其中两个碱基的 metaclasses 是 not subclass 彼此(与Python 上例中的错误信息!)。事实上,只有结果 class 的 metaclass 是基元 metaclass 的 subclass,并且这个 metaclass 是动态创建的:
>>> class MetaMeta(type):
... def __call__(mcls, name, bases, methods):
... metabases = set(type(X) for X in bases)
... metabases.add(mcls)
... if len(metabases) > 1:
... mcls = type(''.join([X.__name__ for X in metabases]), tuple(metabases), {})
... return mcls.__new__(mcls, name, bases, methods)
...
>>> class Meta1(type):
... __metaclass__ = MetaMeta
...
>>> class Meta2(type):
... __metaclass__ = MetaMeta
...
>>> class C1:
... __metaclass__ = Meta1
...
>>> class C2:
... __metaclass__ = Meta2
...
>>> type(C1)
<class '__main__.Meta1'>
>>> type(C2)
<class '__main__.Meta2'>
>>> class C3(C1,C2): pass
...
>>> type(C3)
<class '__main__.Meta1Meta2'>
不太有趣的地方:前面的例子在 Python 中不起作用 3. 如果我理解正确,Python 2 创建 class 并检查其元class 是其所有碱基的 subclass,而 Python 3 first 检查是否有一个碱基的 metaclass 是 superclass 所有其他基础的 metaclasses,只有 then 创建新的 class。从我的角度来看,这是一种倒退。但这应该是我即将 post...
的一个新问题的主题编辑: 新问题是here