这是How to pass arguments to the metaclass from the class definition?问题的Python 3.x版本,由请求单独列出,因为答案与Python 2.x有很大不同。
在Python 3.x中,如何将参数传递给元类的__prepare__
,__new__
和__init__
函数,以便类作者可以向元类提供输入如何创建课程?
作为我的用例,我使用元类来启用类及其子类的自动注册到PyYAML中以加载/保存YAML文件。这涉及PyYAML的股票YAMLObjectMetaClass
中没有的额外运行时逻辑。另外,我想允许类作者有选择地指定PyYAML用来表示用于构造和表示的类和/或函数对象的标记/标记格式模板。我已经发现我无法使用PyYAML YAMLObjectMetaClass
的子类来实现这一目标 - "因为我们无法访问实际内容__new__
"中的类对象根据我的代码注释 - 所以我写了自己的元类,包含了PyYAML的注册函数。
最终,我想做一些事情:
from myutil import MyYAMLObjectMetaClass
class MyClass(metaclass=MyYAMLObjectMetaClass):
__metaclassArgs__ = ()
__metaclassKargs__ = {"tag": "!MyClass"}
...其中__metaclassArgs__
和__metaclassKargs__
将成为__prepare__
的{{1}},__new__
和__init__
方法的参数正在创建MyYAMLObjectMetaClass
类对象。
当然,我可以使用"保留的属性名称" the Python 2.x version of this question中列出的方法,但我知道有更优雅的方法。
答案 0 :(得分:12)
在深入研究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及更早版本;请参阅"更新"下方),或者由于传递了太多参数而导致TypeError
异常。这意味着 - 当以这种方式传递元类参数时 - 我们总是必须实现MyMetaClass.__new__
和MyMetaClass.__init__
以使我们的自定义关键字参数不会到达基类type.__new__
和{{ 1}}方法。 type.__init__
似乎优雅地处理了额外的关键字参数(因此,为什么我在示例中传递了它们,以防万一我不了解的某些功能依赖于type.__prepare__
),因此定义**kargs
是可选的。
在Python 3.6中,显示type.__prepare__
已调整,type
现在可以优雅地处理额外的关键字参数。您仍然需要定义type.__init__
(抛出type.__new__
例外)。
在Python 3中,您通过关键字参数而不是类属性指定元类:
TypeError: __init_subclass__() takes no keyword arguments
此声明大致转换为:
class MyClass(metaclass=MyMetaClass):
pass
...其中MyClass = metaclass(name, bases, **kargs)
是"元类"的值您传入的参数,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
大部分信息来自Python's Documentation on "Customizing Class Creation"。
答案 1 :(得分:2)
这里是我对other question的答案中关于元类参数的代码的一个版本,该代码已经更新,因此它可以在两个 Python 2 和 3.它基本上与Benjamin Peterson的six模块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
答案 2 :(得分:0)
在Python 3中,您通过关键字参数而不是类属性指定元类:
值得一提的是,这种风格并不向后兼容python 2.如果你想支持python 2和3,你应该使用:
Unable to resolve module ../../myModule from index.ios.js
答案 3 :(得分:0)
这是在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
您还可以为仅使用带有额外参数的__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