[英]How to pass arguments to the metaclass from the class definition in Python 3.x?
This is a Python 3.x version of the How to pass arguments to the metaclass from the class definition? 这是如何从类定义向元类传递参数的Python 3.x版本? question, listed separately by request since the answer is significantly different from Python 2.x.
问题,由请求单独列出,因为答案与Python 2.x有很大不同。
In Python 3.x, how do I pass arguments to a metaclass's __prepare__
, __new__
, and __init__
functions so a class author can give input to the metaclass on how the class should be created? 在Python 3.x中,如何将参数传递给元类的
__prepare__
__new__
, __init__
__new__
和__init__
函数,以便类作者可以向元类提供有关如何创建类的输入?
As my use case, I'm using metaclasses to enable automatic registration of classes and their subclasses into PyYAML for loading/saving YAML files. 作为我的用例,我正在使用元类来自动将类及其子类注册到PyYAML中以加载/保存YAML文件。 This involves some extra runtime logic not available in PyYAML's stock
YAMLObjectMetaClass
. 这涉及PyYAML库存
YAMLObjectMetaClass
没有的额外运行时逻辑。 In addition, I want to allow class authors to optionally specify the tag/tag-format-template that PyYAML uses to represent the class and/or the function objects to use for construction and representation. 另外,我想允许类作者有选择地指定PyYAML用来表示用于构造和表示的类和/或函数对象的标记/标记格式模板。 I've already figured out that I can't use a subclass of PyYAML's
YAMLObjectMetaClass
to accomplish this--"because we don't have access to the actual class object in __new__
" according to my code comment--so I'm writing my own metaclass that wraps PyYAML's registration functions. 我已经发现我不能使用PyYAML的
YAMLObjectMetaClass
的子类来完成这个 - “因为根据我的代码注释,我们无法访问__new__
的实际类对象 - 所以我正在写我自己的元类包装了PyYAML的注册函数。
Ultimately, I want to do something along the lines of: 最终,我想做的事情是:
from myutil import MyYAMLObjectMetaClass
class MyClass(metaclass=MyYAMLObjectMetaClass):
__metaclassArgs__ = ()
__metaclassKargs__ = {"tag": "!MyClass"}
...where __metaclassArgs__
and __metaclassKargs__
would be arguments going to the __prepare__
, __new__
, and __init__
methods of MyYAMLObjectMetaClass
when the MyClass
class object is getting created. ...当创建
MyClass
类对象时, __init__
__metaclassArgs__
和__metaclassKargs__
将成为__metaclassKargs__
的__prepare__
__new__
, __init__
__new__
和__init__
方法的MyYAMLObjectMetaClass
。
Of course, I could use the "reserved attribute names" approach listed in the Python 2.x version of this question , but I know there is a more elegant approach available. 当然,我可以使用此问题的Python 2.x版本中列出的“保留属性名称”方法,但我知道有一种更优雅的方法可用。
After digging through Python's official documentation, I found that Python 3.x offers a native method of passing arguments to the metaclass, though not without its flaws. 在深入研究Python的官方文档之后,我发现Python 3.x提供了一种将参数传递给元类的本地方法,尽管并非没有缺陷。
Simply add additional keyword arguments to your class declaration: 只需在类声明中添加其他关键字参数:
class C(metaclass=MyMetaClass, myArg1=1, myArg2=2):
pass
...and they get passed into your metaclass like so: ......它们会像这样传递到你的元类中:
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.
You have to leave kargs
out of the call to type.__new__
and type.__init__
(Python 3.5 and older; see "UPDATE" below) or will get you a TypeError
exception due to passing too many arguments. 您必须将
kargs
从调用中type.__new__
以type.__new__
并type.__init__
(Python 3.5及更早版本;请参阅下面的“更新”)或由于传递过多参数而导致出现TypeError
异常。 This means that--when passing in metaclass arguments in this manner--we always have to implement MyMetaClass.__new__
and MyMetaClass.__init__
to keep our custom keyword arguments from reaching the base class type.__new__
and type.__init__
methods. 这意味着 - 当以这种方式传递元类参数时 - 我们总是必须实现
MyMetaClass.__new__
和MyMetaClass.__init__
以保持我们的自定义关键字参数不会到达基类type.__new__
和type.__init__
方法。 type.__prepare__
seems to handle the extra keyword arguments gracefully (hence why I pass them through in the example, just in case there's some functionality I don't know about that relies on **kargs
), so defining type.__prepare__
is optional. type.__prepare__
似乎优雅地处理额外的关键字参数(因此我在示例中传递它们,以防万一有一些我不知道的功能依赖于**kargs
),因此定义type.__prepare__
是可选的。
In Python 3.6, it appears type
was adjusted and type.__init__
can now handle extra keyword arguments gracefully. 在Python 3.6中,它显示
type
已调整, type.__init__
现在可以优雅地处理额外的关键字参数。 You'll still need to define type.__new__
(throws TypeError: __init_subclass__() takes no keyword arguments
exception). 您仍然需要定义
type.__new__
TypeError: __init_subclass__() takes no keyword arguments
抛出TypeError: __init_subclass__() takes no keyword arguments
异常)。
In Python 3, you specify a metaclass via keyword argument rather than class attribute: 在Python 3中,您通过关键字参数而不是class属性指定元类:
class MyClass(metaclass=MyMetaClass):
pass
This statement roughly translates to: 该声明大致转化为:
MyClass = metaclass(name, bases, **kargs)
...where metaclass
is the value for the "metaclass" argument you passed in, name
is the string name of your class ( 'MyClass'
), bases
is any base classes you passed in (a zero-length tuple ()
in this case), and kargs
is any uncaptured keyword arguments (an empty dict
{}
in this case). ...其中
metaclass
是您传入的“metaclass”参数的值, name
是您的类的字符串名称( 'MyClass'
), bases
是您传入的任何基类(此处为零长度元组()
case), kargs
是任何未捕获的关键字参数(在这种情况下为空dict
{}
)。
Breaking this down further, the statement roughly translates to: 进一步打破这一点,该声明大致转化为:
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)
...where kargs
is always the dict
of uncaptured keyword arguments we passed in to the class definition. ...其中
kargs
总是我们传递给类定义的未捕获关键字参数的dict
。
Breaking down the example I gave above: 打破我上面给出的例子:
class C(metaclass=MyMetaClass, myArg1=1, myArg2=2):
pass
...roughly translates to: ...大致翻译为:
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)
Most of this information came from Python's Documentation on "Customizing Class Creation" . 大部分信息来自Python关于“自定义类创建”的文档 。
Here's a version of the code from my answer to that other question about metaclass arguments which has been updated so that it'll work in both Python 2 and 3. It essentially does the same thing that Benjamin Peterson's six module's with_metaclass()
function does — which namely is to explicitly create a new base class using the desired metaclass on-the-fly, whenever needed and thereby avoiding errors due to the metaclass syntax differences between the two versions of Python (because the way to do that didn't change). 下面是我的回答是一个代码的版本, 其他问题已更新,它会在这两个 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)
Output: 输出:
dict_proxy({'to': 'and', '__module__': '__main__', 'args': 'spam',
'eggs': 'eggs', '__doc__': None})
spam and eggs
In Python 3, you specify a metaclass via keyword argument rather than class attribute:
在Python 3中,您通过关键字参数而不是class属性指定元类:
It's worth to say, that this style is not backward compatible to python 2. If you want to support both python 2 and 3, you should use: 值得一提的是,这种风格并不向后兼容python 2.如果你想同时支持python 2和3,你应该使用:
from six import with_metaclass
# or
from future.utils import with_metaclass
class Form(with_metaclass(MyMetaClass, object)):
pass
Here's the simplest way to pass arguments to a metaclass in Python 3: 这是在Python 3中将参数传递给元类的最简单方法:
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
You can also create a base class for metaclasses that only use __init__
with extra arguments: 您还可以为仅使用带有额外参数的
__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.