如何从类定义中将参数传递给元类?
Posted
技术标签:
【中文标题】如何从类定义中将参数传递给元类?【英文标题】:How to pass arguments to the metaclass from the class definition? 【发布时间】:2012-11-25 13:56:26 【问题描述】:我正在尝试在 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()
有没有更清洁的方法?
【问题讨论】:
目前还不清楚您要在这里实现什么目标,您能否提供一些关于您的最终目标的信息?我不确定你是否需要__metaclass__
。
我不明白你为什么要这样做。也许没有函数会更好,只有元类?
我和他们在一起;通常这种问题是,相对于这里的众多问题,你实际上已经过度考虑了这个问题并设计了一个比可能需要的复杂得多的解决方案。洒了豆子,赤身裸体,你在想什么?但我确实喜欢你的问题
你说,“但这需要我做..”我不明白你想实现什么使用代码。您需要一个带有参数的类构建器,然后您需要实例化该类。这正是你所展示的。向我们展示您想要访问的代码,目前尚不清楚您要去哪里。
【参考方案1】:
虽然这个问题是针对 Python 2.7 的,并且已经有了一个很好的答案,但我对 Python 3.3 也有同样的问题,而且这个帖子是我在 Google 上找到的最接近答案的问题。通过深入研究 Python 文档,我为 Python 3.x 找到了更好的解决方案,并且我正在与其他来这里寻找 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, **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.
您必须将kwargs
排除在对type.__new__
和type.__init__
的调用之外(Python 3.5 及更早版本;请参阅“更新”),否则由于传递的参数过多,您将获得TypeError
异常。这意味着——当以这种方式传递元类参数时——我们总是必须实现MyMetaClass.__new__
和MyMetaClass.__init__
以防止我们的自定义关键字参数到达基类type.__new__
和type.__init__
方法。 type.__prepare__
似乎可以优雅地处理额外的关键字参数(因此为什么我在示例中传递它们,以防有一些我不知道的功能依赖于 **kwargs
),因此定义 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, **kwargs)
...其中metaclass
是您传入的“元类”参数的值,name
是您的类的字符串名称('MyClass'
),bases
是您传入的任何基类(零长度元组 ()
在这种情况下),kwargs
是任何未捕获的关键字参数(在这种情况下是空的 dict
)。
进一步分解,该语句大致翻译为:
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)
...其中kwargs
始终是我们传递给类定义的未捕获关键字参数的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's Documentation on "Customizing Class Creation"。
【讨论】:
这非常有帮助。我希望你能发布一个专门关于 Python 3 的新问题,然后自己回答——我很难找到它。 cisco.safaribooksonline.com/9781449357337/… 完成!感谢您的建议!对不起,我花了这么长时间才看到它!:***.com/questions/27258557/… @JohnCrawford,这是很棒的工作。几个月来,即使彻底阅读文档,我也无法解决这个问题。您的示例需要添加到官方文档中。 值得一提的是,即使你只是继承了一个已经指定了自定义元类的类,你也可以添加额外的 kwargs。例如。class D(C, myArg2=2)
【参考方案2】:
是的,有一个简单的方法可以做到这一点。在元类的__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 的代码,其中包括显式调用元类并使用创建的类作为基类被定义的那个。
class MyClass(MyMetaClass("NewBaseClass", (object,), )):
meta_args = ['spam', 'and', 'eggs']
Python 3 中的类构造也进行了修改,并添加了支持以允许以其他方式传递参数,并且在某些情况下使用它们可能比此处显示的技术更容易。这完全取决于您要完成的工作。
请参阅@John Crawford 的详细信息 answer,了解新版本 Python 中的流程描述。
【讨论】:
请参阅this answer 了解适用于 Python 2 和 3 的代码版本。以上是关于如何从类定义中将参数传递给元类?的主要内容,如果未能解决你的问题,请参考以下文章