如何从Python 3.x中的类定义将参数传递给元类? [英] How to pass arguments to the metaclass from the class definition in Python 3.x?
问题描述
这是
最终,我想按照以下方式做点事情: ...在创建 当然,我可以使用解决方案from myutil import MyYAMLObjectMetaClass
class MyClass(metaclass=MyYAMLObjectMetaClass):
__metaclassArgs__ = ()
__metaclassKargs__ = {"tag": "!MyClass"}
MyClass
类对象时,其中__metaclassArgs__
和__metaclassKargs__
是进入MyYAMLObjectMetaClass
的__prepare__
,__new__
和__init__
方法的参数.>
在仔细阅读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.__init__
的调用之外(Python 3.5及更低版本;请参见下面的"UPDATE"),否则由于传递了太多参数而将为您提供TypeError
异常.这意味着-以这种方式传递元类参数时-我们始终必须实现MyMetaClass.__new__
和MyMetaClass.__init__
,以防止自定义关键字参数到达基类type.__new__
和type.__init__
方法. type.__prepare__
似乎可以很好地处理额外的关键字参数(因此,为什么在示例中将它们传递给我,以防万一某些我不知道的功能依赖于**kargs
),因此定义type.__prepare__
是可选的.
更新
在Python 3.6中,看来type
已调整,并且type.__init__
现在可以正常处理其他关键字参数.您仍然需要定义type.__new__
(引发TypeError: __init_subclass__() takes no keyword arguments
异常).
故障
在Python 3中,您通过关键字参数而不是类属性来指定元类:
class MyClass(metaclass=MyMetaClass):
pass
此语句大致翻译为:
MyClass = metaclass(name, bases, **kargs)
...其中,metaclass
是您传入的元类"参数的值,name
是您的类的字符串名称('MyClass'
),bases
是您传入的任何基类(长度为零的元组()
(在这种情况下),而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文档类创建" .
This is a Python 3.x version of the How to pass arguments to the metaclass from the class definition? question, listed separately by request since the answer is significantly different from 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?
As my use case, I'm using metaclasses to enable automatic registration of classes and their subclasses into PyYAML for loading/saving YAML files. This involves some extra runtime logic not available in PyYAML's stock 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. 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.
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.
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.
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.
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. 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. 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.
UPDATE
In Python 3.6, it appears type
was adjusted and type.__init__
can now handle extra keyword arguments gracefully. You'll still need to define type.__new__
(throws TypeError: __init_subclass__() takes no keyword arguments
exception).
Breakdown
In Python 3, you specify a metaclass via keyword argument rather than class attribute:
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).
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.
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 3.x中的类定义将参数传递给元类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!