我正在尝试在python 2.7中动态生成类,我想知道你是否可以轻松地从类对象传递参数到元类。
我已经阅读了this帖子,这很棒,但并没有完全回答这个问题。在我正在做的那一刻:
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
...
但这需要我做以下
MyClassClass = class_factory('spam', 'and', 'eggs')
my_instance = MyClassClass()
有更清洁的方法吗?
答案 0 :(得分:26)
虽然问题是针对Python 2.7并且已经有了一个很好的答案,但我对Python 3.3有同样的问题,这个帖子最接近我能找到的答案。我通过挖掘Python文档找到了一个更好的Python 3.x解决方案,我正在为寻找Python 3.x版本的其他人分享我的发现。
在深入研究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__
),所以定义{{ 1}}是可选的。
在Python 3.6中,显示**kargs
已调整,type.__prepare__
现在可以优雅地处理额外的关键字参数。您仍然需要定义type
(抛出type.__init__
例外)。
在Python 3中,您通过关键字参数而不是类属性指定元类:
type.__new__
此声明大致转换为:
TypeError: __init_subclass__() takes no keyword arguments
...其中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
大部分信息来自Python's Documentation on "Customizing Class Creation"。
答案 1 :(得分:12)
是的,有一种简单的方法可以做到这一点。在元类的__new__()
方法中,只需检入作为最后一个参数传递的类字典。 class
语句中定义的任何内容都将存在。例如:
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
输出:
['__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
<强>更新强>
上面的代码只能在Python 2中使用,因为在Python 3中以不兼容的方式更改了指定元类的语法。
要使它在Python 3中运行(但不再在Python 2中),这非常简单,只需要将MyClass
的定义更改为:
class MyClass(metaclass=MyMetaClass):
meta_args = ['spam', 'and', 'eggs']
通过显式调用元类并使用创建的类作为基础,通过“动态”创建基类,也可以解决语法差异并生成适用于Python 2和3的代码。 p>
class MyClass(MyMetaClass("NewBaseClass", (object,), {})):
meta_args = ['spam', 'and', 'eggs']
类构造Python 3也已经过修改,并且添加了支持,允许其他方式传递参数,在某些情况下使用它们可能比这里显示的技术更容易。这一切都取决于你想要完成的事情。
请参阅@John Crawford的详细answer,了解新版Python中的过程。