为什么类定义的metaclass关键字参数接受一个可调用? [英] Why does the class definition's metaclass keyword argument accept a callable?

查看:97
本文介绍了为什么类定义的metaclass关键字参数接受一个可调用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

背景



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



  • if没有基础和没有明确的元类,则使用type()

  • 如果给定了显式元类,并且它不是type()的实例,元类

  • 如果type()的实例被给定为显式元类,或者定义了基数,则使用最多派生的元类


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

  class MyMetaclass(type):
pass

def metaclass_callable(name,基础,命名空间):
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 的元类
code>: metaclass_callable MyMetaclass ?文档中的第二条规则说,提供的可调用直接用作元类。但是,从$ <$ p


  • 开始, $ > MyClass MyDerived 有类型 MyMetaclass

  • metaclass_callable 被调用一次,然后显示为不可恢复,

  • 派生类不使用只要我能告诉) metaclass_callable (他们使用 MyMetaclass )。



问题2



您可以使用可调用项执行任何操作, 类型?关于你的第一个问题,元类应该是 MyMetaclass。

(这是如此):

  (MyDerived))
< class'__main __。MyMetaclass'> < class'__main __。MyMetaclass'>

原因是如果元类不是类型python的实例,通过传递这些参数调用methaclass到它 name,bases,ns,** kwds (见 new_class ),因为你返回你的真正的元类



关于第二个问题:


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


没有特殊用途,元类,这是因为从类创建实例总是通过调用 __调用__ 方法调用元类:

 元类.__ call __()

可以传递任何可调用作为您的元类。例如,如果你用嵌套函数测试它的结果仍然是相同的:

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

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

在[23] ),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 __

这里是源代码:

 #为类创建提供符合PEP 3115的机制
def new_class name,bases =(),kwds = None,exec_body = None):
使用合适的元类动态创建类对象。
meta,ns,kwds = prepare_class base,kwds)
如果exec_body不是None:
exec_body(ns)
return meta(name,bases,ns,** kwds)

def prepare_class name,bases =(),kwds = None):
调用相应元类的__prepare__方法。

作为3元组返回(元类,命名空间,kwds)

*元类*是适当的元类
*命名空间*是准备的类命名空间
* kwds *是除去了
'metaclass'条目的传入kwds参数的更新副本。如果没有传入kwds参数,这将
是空的dict。

如果kwds为None:
kwds = {}
else:
kwds = dict(kwds)#不要更改提供的映射
if'metaclass'in kwds:
meta = kwds.pop('metaclass')
else:
if bases:
meta = type(bases [0])
else:
meta = type
如果isinstance(meta,type):
#当meta是一个类型时,我们首先确定最多派生的元类
#直接调用初始候选项
meta = _calculate_meta(meta,bases)
如果hasattr(meta,'__prepare__'):
ns = meta .__ prepare __(name,bases,** kwds)
else:
ns = {}
return meta,ns,kwds


def _calculate_meta(meta,bases):
计算最衍生的元类
winner = meta
在基数中的基数:
base_meta = type(base)
如果issubclass(winner,base_meta):
继续
如果issubclass(base_meta,winner):
winner = base_meta
continue
#else:
raise TypeError(metaclass conflict:
一个派生类
的元类必须是所有基类的元类的一个(非严格)子类

return winner







1.请注意, em> new_class 函数和返回之前。


Background

The Python 3 documentation clearly describes how the metaclass of a class is determined:

  • 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

Therefore, according to the second rule, it is possible to specify a metaclass using a callable. E.g.,

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))

Question 1

Is the metaclass of MyClass: metaclass_callable or MyMetaclass? The second rule in the documentation says that the provided callable "is used directly as the metaclass". However, it seems to make more sense to say that the metaclass is MyMetaclass since

  • MyClass and MyDerived have type MyMetaclass,
  • metaclass_callable is called once and then appears to be unrecoverable,
  • derived classes do not use (as far as I can tell) metaclass_callable in any way (they use MyMetaclass).

Question 2

Is there anything you can do with a callable that you can't do with an instance of type? What is the purpose of accepting an arbitrary callable?

解决方案

Regarding your first question the metaclass should be MyMetaclass (which it's so):

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

The reason is that if the metaclass is not an instance of type python calls the methaclass by passing these arguments to it name, bases, ns, **kwds (see new_class) and since you are returning your real metaclass in that function it gets the correct type for metaclass.

And about the second question:

What is the purpose of accepting an arbitrary callable?

There is no special purpose, it's actually the nature of metaclasses which is because that making an instance from a class always calls the metaclass by calling it's __call__ method:

Metaclass.__call__()

Which means that you can pass any callable as your metaclass. So for example if you test it with a nested function the result will still be the same:

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'>


For more info here is how Python crates a class:

It calls the new_class function which it calls prepare_class inside itself, then as you can see inside the prepare_class python calls the __prepare__ method of the appropriate metaclass, beside of finding the proper meta (using _calculate_meta function ) and creating the appropriate namespace for the class.

So all in one here is the hierarchy of executing a metacalss's methods:

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

And here is the source code:

# 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. Note that it get called implicitly inside the new_class function and before the return.

这篇关于为什么类定义的metaclass关键字参数接受一个可调用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆