[英]How to pass arguments to the metaclass from the class definition?
I'm trying to dynamically generate classes in python 2.7, and am wondering if you can easily pass arguments to the metaclass from the class object.我正在尝试在 python 2.7 中动态生成类,并且想知道您是否可以轻松地将参数从类对象传递给元类。
I've read this post, which is awesome, but doesn't quite answer the question.我读过这篇文章,很棒,但没有完全回答问题。 at the moment I am doing:
目前我正在做:
def class_factory(args, to, meta_class):
Class MyMetaClass(type):
def __new__(cls, class_name, parents, attrs):
attrs['args'] = args
attrs['to'] = to
attrs['eggs'] = meta_class
class MyClass(object):
metaclass = MyMetaClass
...
but this requires me to do the following但这需要我做以下事情
MyClassClass = class_factory('spam', 'and', 'eggs')
my_instance = MyClassClass()
Is there a cleaner way of doing this?有更清洁的方法吗?
While the question is for Python 2.7 and already has an excellent answer, I had the same question for Python 3.3 and this thread was the closest thing to an answer I could find with Google.虽然这个问题是针对 Python 2.7 的,并且已经有了一个很好的答案,但我对 Python 3.3 也有同样的问题,这个线程是我在谷歌上找到的最接近答案的东西。 I found a better solution for Python 3.x by digging through the Python documentation, and I'm sharing my findings for anyone else coming here looking for a Python 3.x version.
通过深入研究 Python 文档,我找到了适用于 Python 3.x 的更好解决方案,并且我将与来这里寻找 Python 3.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, **kwargs):
#kwargs = {"myArg1": 1, "myArg2": 2}
return super().__prepare__(name, bases, **kwargs)
def __new__(metacls, name, bases, namespace, **kwargs):
#kwargs = {"myArg1": 1, "myArg2": 2}
return super().__new__(metacls, name, bases, namespace)
#DO NOT send "**kwargs" 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, **kwargs):
#myArg1 = 1 #Included as an example of capturing metaclass args as positional args.
#kwargs = {"myArg2": 2}
super().__init__(name, bases, namespace)
#DO NOT send "**kwargs" 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 kwargs
out of the call to type.__new__
and type.__init__
(Python 3.5 and older; see "UPDATE") or will get you a TypeError
exception due to passing too many arguments.您必须将
kwargs
排除在对type.__new__
和type.__init__
(Python 3.5 及更早版本;参见“UPDATE”)的调用之外,否则会因传递太多参数而导致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 **kwargs
), so defining type.__prepare__
is optional. type.__prepare__
似乎可以优雅地处理额外的关键字参数(因此我在示例中传递它们的原因,以防万一有一些我不知道依赖于**kwargs
的功能),所以定义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
异常)。
In Python 3, you specify a metaclass via keyword argument rather than class attribute:在 Python 3 中,您通过关键字参数而不是类属性指定元类:
class MyClass(metaclass=MyMetaClass):
pass
This statement roughly translates to:这句话大致翻译为:
MyClass = metaclass(name, bases, **kwargs)
...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 kwargs
is any uncaptured keyword arguments (an empty dict
{}
in this case). ...其中
metaclass
是您传入的“元类”参数的值, name
是您的类的字符串名称( 'MyClass'
), bases
是您传入的任何基类(此中的零长度元组()
case), kwargs
是任何未捕获的关键字参数(在这种情况下是一个空的dict
{}
)。
Breaking this down further, the statement roughly translates to:进一步分解,该声明大致翻译为:
namespace = metaclass.__prepare__(name, bases, **kwargs) #`metaclass` passed implicitly since it's a class method.
MyClass = metaclass.__new__(metaclass, name, bases, namespace, **kwargs)
metaclass.__init__(MyClass, name, bases, namespace, **kwargs)
...where kwargs
is always the dict
of uncaptured keyword arguments we passed in to the class definition. ...其中
kwargs
始终是我们传递给类定义的未捕获关键字参数的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 的“自定义类创建”文档。
Yes, there's an easy way to do it.是的,有一种简单的方法可以做到这一点。 In the metaclass's
__new__()
method just check in the class dictionary passed as the last argument.在元类的
__new__()
方法中,只需检查作为最后一个参数传递的类字典。 Anything defined in the class
statement will be there. class
语句中定义的任何内容都将存在。 For example:例如:
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)
class MyClass(object):
__metaclass__ = MyMetaClass
meta_args = ['spam', 'and', 'eggs']
myobject = MyClass()
from pprint import pprint
pprint(dir(myobject))
print myobject.args, myobject.to, myobject.eggs
Output:输出:
['__class__',
'__delattr__',
'__dict__',
'__doc__',
'__format__',
'__getattribute__',
'__hash__',
'__init__',
'__metaclass__',
'__module__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'__weakref__',
'args',
'eggs',
'to']
spam and eggs
Update更新
The code above will only work in Python 2 because syntax for specifying a metaclass was changed in an incompatible way in Python 3.上面的代码只能在 Python 2 中工作,因为指定元类的语法在 Python 3 中以不兼容的方式发生了变化。
To make it work in Python 3 (but no longer in Python 2) is super simple to do and only requires changing the definition of MyClass
to:要使其在 Python 3 中工作(但不再在 Python 2 中工作)非常简单,只需要将
MyClass
的定义更改为:
class MyClass(metaclass=MyMetaClass):
meta_args = ['spam', 'and', 'eggs']
It's also possible to workaround the syntax differences and produce code that works in both Python 2 and 3 by creating base classes "on-the-fly" which involves explicitly invoking the metaclass and using the class that is created as the base class of the one being defined.也可以通过“即时”创建基类来解决语法差异并生成适用于 Python 2 和 3 的代码,这涉及显式调用元类并使用创建的类作为基类被定义。
class MyClass(MyMetaClass("NewBaseClass", (object,), {})):
meta_args = ['spam', 'and', 'eggs']
Class construction in Python 3 has also been modified and support was added that allows other ways of passing arguments, and in some cases using them might be easier than the technique shown here. Python 3 中的类构造也进行了修改,并添加了允许其他传递参数方式的支持,在某些情况下,使用它们可能比此处显示的技术更容易。 It all depends on what you're trying to accomplish.
这完全取决于您要完成的任务。
See @John Crawford's detailed answer for a description of of the process in the new versions of Python.有关新版本 Python 中流程的描述,请参阅@John Crawford 的详细回答。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.