繁体   English   中英

如何从Python 3.x中的类定义传递参数到元类?

[英]How to pass arguments to the metaclass from the class definition in Python 3.x?

这是如何从类定义向元类传递参数的Python 3.x版本 问题,由请求单独列出,因为答案与Python 2.x有很大不同。


在Python 3.x中,如何将参数传递给元类的__prepare__ __new____init__ __new____init__函数,以便类作者可以向元类提供有关如何创建类的输入?

作为我的用例,我正在使用元类来自动将类及其子类注册到PyYAML中以加载/保存YAML文件。 这涉及PyYAML库存YAMLObjectMetaClass没有的额外运行时逻辑。 另外,我想允许类作者有选择地指定PyYAML用来表示用于构造和表示的类和/或函数对象的标记/标记格式模板。 我已经发现我不能使用PyYAML的YAMLObjectMetaClass的子类来完成这个 - “因为根据我的代码注释,我们无法访问__new__的实际类对象 - 所以我正在写我自己的元类包装了PyYAML的注册函数。

最终,我想做的事情是:

from myutil import MyYAMLObjectMetaClass

class MyClass(metaclass=MyYAMLObjectMetaClass):
    __metaclassArgs__ = ()
    __metaclassKargs__ = {"tag": "!MyClass"}

...当创建MyClass类对象时, __init__ __metaclassArgs____metaclassKargs__将成为__metaclassKargs____prepare__ __new____init__ __new____init__方法的MyYAMLObjectMetaClass

当然,我可以使用此问题的Python 2.x版本中列出的“保留属性名称”方法,但我知道有一种更优雅的方法可用。

在深入研究Python的官方文档之后,我发现Python 3.x提供了一种将参数传递给元类的本地方法,尽管并非没有缺陷。

只需在类声明中添加其他关键字参数:

class C(metaclass=MyMetaClass, myArg1=1, myArg2=2):
  pass

......它们会像这样传递到你的元类中:

class MyMetaClass(type):

  @classmethod
  def __prepare__(metacls, name, bases, **kargs):
    #kargs = {"myArg1": 1, "myArg2": 2}
    return super().__prepare__(name, bases, **kargs)

  def __new__(metacls, name, bases, namespace, **kargs):
    #kargs = {"myArg1": 1, "myArg2": 2}
    return super().__new__(metacls, name, bases, namespace)
    #DO NOT send "**kargs" to "type.__new__".  It won't catch them and
    #you'll get a "TypeError: type() takes 1 or 3 arguments" exception.

  def __init__(cls, name, bases, namespace, myArg1=7, **kargs):
    #myArg1 = 1  #Included as an example of capturing metaclass args as positional args.
    #kargs = {"myArg2": 2}
    super().__init__(name, bases, namespace)
    #DO NOT send "**kargs" to "type.__init__" in Python 3.5 and older.  You'll get a
    #"TypeError: type.__init__() takes no keyword arguments" exception.

您必须将kargs从调用中type.__new__type.__new__type.__init__ (Python 3.5及更早版本;请参阅下面的“更新”)或由于传递过多参数而导致出现TypeError异常。 这意味着 - 当以这种方式传递元类参数时 - 我们总是必须实现MyMetaClass.__new__MyMetaClass.__init__以保持我们的自定义关键字参数不会到达基类type.__new__type.__init__方法。 type.__prepare__似乎优雅地处理额外的关键字参数(因此我在示例中传递它们,以防万一有一些我不知道的功能依赖于**kargs ),因此定义type.__prepare__是可选的。

UPDATE

在Python 3.6中,它显示type已调整, type.__init__现在可以优雅地处理额外的关键字参数。 您仍然需要定义type.__new__ TypeError: __init_subclass__() takes no keyword arguments抛出TypeError: __init_subclass__() takes no keyword arguments异常)。

分解

在Python 3中,您通过关键字参数而不是class属性指定元类:

class MyClass(metaclass=MyMetaClass):
  pass

该声明大致转化为:

MyClass = metaclass(name, bases, **kargs)

...其中metaclass是您传入的“metaclass”参数的值, name是您的类的字符串名称( 'MyClass' ), bases是您传入的任何基类(此处为零长度元组() case), kargs是任何未捕获的关键字参数(在这种情况下为空dict {} )。

进一步打破这一点,该声明大致转化为:

namespace = metaclass.__prepare__(name, bases, **kargs)  #`metaclass` passed implicitly since it's a class method.
MyClass = metaclass.__new__(metaclass, name, bases, namespace, **kargs)
metaclass.__init__(MyClass, name, bases, namespace, **kargs)

...其中kargs总是我们传递给类定义的未捕获关键字参数的dict

打破我上面给出的例子:

class C(metaclass=MyMetaClass, myArg1=1, myArg2=2):
  pass

...大致翻译为:

namespace = MyMetaClass.__prepare__('C', (), myArg1=1, myArg2=2)
#namespace={'__module__': '__main__', '__qualname__': 'C'}
C = MyMetaClass.__new__(MyMetaClass, 'C', (), namespace, myArg1=1, myArg2=2)
MyMetaClass.__init__(C, 'C', (), namespace, myArg1=1, myArg2=2)

大部分信息来自Python关于“自定义类创建”的文档

下面是我的回答是一个代码的版本, 其他问题已更新,它会在这两个 Python 2 3的工作有关元类的论点它本质上是做本杰明·彼得森的同样的事情, 6个模块的with_metaclass()函数的作用-即在需要时使用所需的元类显式创建一个新的基类,从而避免由于两个版本的Python之间的元类语法差异而导致的错误(因为这样做的方式没有改变) 。

from __future__ import print_function
from pprint import pprint

class MyMetaClass(type):
    def __new__(cls, class_name, parents, attrs):
        if 'meta_args' in attrs:
            meta_args = attrs['meta_args']
            attrs['args'] = meta_args[0]
            attrs['to'] = meta_args[1]
            attrs['eggs'] = meta_args[2]
            del attrs['meta_args'] # clean up
        return type.__new__(cls, class_name, parents, attrs)

# Creates base class on-the-fly using syntax which is valid in both
# Python 2 and 3.
class MyClass(MyMetaClass("NewBaseClass", (object,), {})):
    meta_args = ['spam', 'and', 'eggs']

myobject = MyClass()

pprint(vars(MyClass))
print(myobject.args, myobject.to, myobject.eggs)

输出:

dict_proxy({'to': 'and', '__module__': '__main__', 'args': 'spam',
            'eggs': 'eggs', '__doc__': None})
spam and eggs

在Python 3中,您通过关键字参数而不是class属性指定元类:

值得一提的是,这种风格并不向后兼容python 2.如果你想同时支持python 2和3,你应该使用:

from six import with_metaclass
# or
from future.utils import with_metaclass

class Form(with_metaclass(MyMetaClass, object)):
    pass

这是在Python 3中将参数传递给元类的最简单方法:

Python 3.x

class MyMetaclass(type):
    def __new__(mcs, name, bases, namespace, **kwargs):
        return super().__new__(mcs, name, bases, namespace)

    def __init__(cls, name, bases, namespace, custom_arg='default'):
        super().__init__(name, bases, namespace)

        print('Argument is:', custom_arg)


class ExampleClass(metaclass=MyMetaclass, custom_arg='something'):
    pass

您还可以为仅使用带有额外参数的__init__元类创建基类:

class ArgMetaclass(type):
    def __new__(mcs, name, bases, namespace, **kwargs):
        return super().__new__(mcs, name, bases, namespace)


class MyMetaclass(ArgMetaclass):
    def __init__(cls, name, bases, namespace, custom_arg='default'):
        super().__init__(name, bases, namespace)

        print('Argument:', custom_arg)


class ExampleClass(metaclass=MyMetaclass, custom_arg='something'):
    pass

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM