How to pass arguments to the metaclass from the class definition in Python 3.x?
这是如何从类定义向元类传递参数的python 3.x版本?问题,根据请求单独列出,因为答案与python 2.x明显不同。
在python 3.x中,如何将参数传递给元类的
作为我的用例,我使用元类来实现类及其子类自动注册到pyyaml中,以便加载/保存yaml文件。这涉及到Pyyaml的股票
最后,我想做的事情是:
1 2 3 4 5 | from myutil import MyYAMLObjectMetaClass class MyClass(metaclass=MyYAMLObjectMetaClass): __metaclassArgs__ = () __metaclassKargs__ = {"tag":"!MyClass"} |
…当
当然,我可以使用这个问题的python 2.x版本中列出的"保留属性名"方法,但我知道有一种更优雅的方法可用。
在深入研究了python的官方文档之后,我发现python 3.x提供了一种向元类传递参数的本地方法,尽管它并非没有缺陷。
只需在类声明中添加其他关键字参数:
1 2 | class C(metaclass=MyMetaClass, myArg1=1, myArg2=2): pass |
…它们会像这样传递到您的元类中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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. |
您必须让
在python 3.6中,似乎已经调整了
在Python3中,通过关键字参数而不是类属性指定元类:
1 2 | class MyClass(metaclass=MyMetaClass): pass |
这句话大致翻译为:
1 | MyClass = metaclass(name, bases, **kargs) |
…其中
进一步细分,该声明大致翻译为:
1 2 3 | 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) |
…其中
分解我上面给出的示例:
1 2 | class C(metaclass=MyMetaClass, myArg1=1, myArg2=2): pass |
…大致翻译为:
1 2 3 4 | 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关于"定制类创建"的文档。
下面是我对另一个关于元类参数的问题的答案中的一个版本的代码,这个问题已经被更新,以便它在Python2和3中都能工作。它本质上与Benjamin Peterson的六个模块的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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) |
输出:
1 2 3 | dict_proxy({'to': 'and', '__module__': '__main__', 'args': 'spam', 'eggs': 'eggs', '__doc__': None}) spam and eggs |
下面是在python 3中向元类传递参数的最简单方法:
Python 3 x1 2 3 4 5 6 7 8 9 10 11 12 | 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 |
您还可以为只使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 |
In Python 3, you specify a metaclass via keyword argument rather than class attribute:
值得一提的是,这种样式与Python2不向后兼容。如果要同时支持python 2和3,则应使用:
1 2 3 4 5 6 | from six import with_metaclass # or from future.utils import with_metaclass class Form(with_metaclass(MyMetaClass, object)): pass |