为什么类定义的metaclass关键字参数接受可调用?

Nei*_*l G 8 python metaclass class python-3.x

背景

Python 3 文档清楚地描述了如何确定类的元类:

  • 如果没有给出base和没有明确的元类,则使用type()
  • 如果给出了显式元类并且它不是type()的实例,那么它将直接用作元类
  • 如果给出了类型()的实例作为显式元类,或者定义了基数,则使用派生最多的元类

因此,根据第二个规则,可以使用可调用来指定元类.例如,

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))
Run Code Online (Sandbox Code Playgroud)

问题1

是元类MyClass:metaclass_callableMyMetaclass?文档中的第二条规则说,提供的可调用"直接用作元类".但是,MyMetaclass从那时起,元类似乎更有意义

  • MyClassMyDerived有类型MyMetaclass,
  • metaclass_callable 被叫一次然后看起来是不可恢复的,
  • 派生类不metaclass_callable以任何方式使用(据我所知)(他们使用MyMetaclass).

问题2

你可以用一个你不能用一个实例做的调用type吗?接受任意可调用的目的是什么?

Kas*_*mvd 7

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

In [7]: print(type(MyClass), type(MyDerived))
<class '__main__.MyMetaclass'> <class '__main__.MyMetaclass'>
Run Code Online (Sandbox Code Playgroud)

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

关于第二个问题:

接受任意可调用的目的是什么?

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

Metaclass.__call__()
Run Code Online (Sandbox Code Playgroud)

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

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'>
Run Code Online (Sandbox Code Playgroud)

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

它调用new_classprepare_class自己调用的函数,然后你可以在prepare_classpython中看到调用__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
Run Code Online (Sandbox Code Playgroud)

1.请注意,它会在new_class函数内部和返回之前隐式调用.