使用装饰器设置类的元类
Posted
技术标签:
【中文标题】使用装饰器设置类的元类【英文标题】:Setting a class' metaclass using a decorator 【发布时间】:2012-06-20 22:33:34 【问题描述】:在this answer 之后,似乎可以在使用以下*定义类之后更改类的元类:
class MyMetaClass(type):
# Metaclass magic...
class A(object):
pass
A = MyMetaClass(A.__name__, A.__bases__, dict(A.__dict__))
定义一个函数
def metaclass_wrapper(cls):
return MyMetaClass(cls.__name__, cls.__bases__, dict(cls.__dict__))
允许我像这样将装饰器应用于类定义,
@metaclass_wrapper
class B(object):
pass
似乎元类魔法应用于B
,但是B
没有__metaclass__
属性。上述方法是否是将元类应用于类定义的明智方法,即使我正在定义和重新定义一个类,或者我最好简单地编写
class B(object):
__metaclass__ = MyMetaClass
pass
我认为这两种方法之间存在一些差异。
*注意,链接问题中的原始答案MyMetaClass(A.__name__, A.__bases__, A.__dict__)
返回一个TypeError
:
TypeError: type() 参数 3 必须是 dict,而不是 dict_proxy
似乎A
(类定义)的__dict__
属性的类型为dict_proxy
,而A
的实例的__dict__
属性的类型有一个类型dict
。为什么是这样?这是 Python 2.x 与 3.x 的区别吗?
【问题讨论】:
【参考方案1】:诚然,我参加聚会有点晚了。不过,我觉得这值得添加。
这是完全可行的。话虽如此,还有很多其他方法可以实现相同的目标。但是,特别是装饰解决方案允许延迟评估 (obj = dec(obj)
),而在类中使用 __metaclass__
则不允许。在典型的装饰风格中,我的解决方案如下。
如果您只是在不更改字典或复制其属性的情况下构建类,您可能会遇到一件棘手的事情。该类之前(装饰之前)具有的任何属性似乎都将丢失。因此,绝对有必要复制这些内容,然后像我在解决方案中所做的那样对其进行调整。
就个人而言,我喜欢能够跟踪对象的包装方式。所以,我添加了__wrapped__
属性,这不是绝对必要的。它还使它更像 Python 3 中的 functools.wraps
用于类。但是,它对内省很有帮助。此外,添加了__metaclass__
以使其更像正常的元类用例。
def metaclass(meta):
def metaclass_wrapper(cls):
__name = str(cls.__name__)
__bases = tuple(cls.__bases__)
__dict = dict(cls.__dict__)
for each_slot in __dict.get("__slots__", tuple()):
__dict.pop(each_slot, None)
__dict["__metaclass__"] = meta
__dict["__wrapped__"] = cls
return(meta(__name, __bases, __dict))
return(metaclass_wrapper)
举个简单的例子,举个例子。
class MetaStaticVariablePassed(type):
def __new__(meta, name, bases, dct):
dct["passed"] = True
return(super(MetaStaticVariablePassed, meta).__new__(meta, name, bases, dct))
@metaclass(MetaStaticVariablePassed)
class Test(object):
pass
这会产生很好的结果...
|1> Test.passed
|.> True
以不太常见但相同的方式使用装饰器...
class Test(object):
pass
Test = metaclass_wrapper(Test)
...正如预期的那样,产生了同样好的结果。
|1> Test.passed
|.> True
【讨论】:
如果有用,请在 PyPI 上的metawrap
包中使用它。非常欢迎对 GitHub 存储库提供反馈。 :) 参考:pypi.org/project/metawrap 参考:github.com/jakirkham/metawrap【参考方案2】:
我对您的问题的总结:“我尝试了一种新的棘手的方法来做一件事,但效果不佳。我应该改用简单的方法吗?”
是的,你应该用简单的方法来做。你还没有说你为什么对发明一种新的方法感兴趣。
【讨论】:
我只是想知道是否有一个装饰器相当于在类定义中设置__metaclass__
属性。我想我的问题的另一部分是我的“装饰器”确实 似乎 工作,但我注意到装饰类没有 __metaclass__
属性,因此两种方法之间显然存在差异;这是为什么?【参考方案3】:
该类没有设置__metaclass__
属性...因为您从未设置它!
使用哪个元类通常由在类块中设置的名称__metaclass__
确定。 __metaclass__
属性不是由元类设置的。因此,如果您直接调用元类而不是设置 __metaclass__
并让 Python 找出它,则不会设置 __metaclass__
属性。
事实上,普通类都是元类type
的实例,所以如果元类总是在其实例上设置__metaclass__
属性,那么每个类都会有一个__metaclass__
属性(其中大部分设置为type
)。
我不会使用您的装饰器方法。它掩盖了一个事实,即涉及元类(以及哪个),仍然是样板文件的一行,并且从(name, bases, attributes)
的 3 个定义特征创建一个类只是为了将这 3 个位从结果中拉回类,扔掉类,用同样的 3 位创建一个新类!
当您在 Python 2.x 中执行此操作时:
class A(object):
__metaclass__ = MyMeta
def __init__(self):
pass
如果你这样写,你会得到大致相同的结果:
attrs =
attrs['__metaclass__'] = MyMeta
def __init__(self):
pass
attrs['__init__'] = __init__
A = attrs.get('__metaclass__', type)('A', (object,), attrs)
实际上计算元类更复杂,因为实际上必须搜索所有基础以确定是否存在元类冲突,以及是否其中一个基础没有 type
作为其元类和 @ 987654334@ 不包含__metaclass__
那么默认元类是祖先的元类而不是type
。这是我希望您的装饰器“解决方案”与直接使用 __metaclass__
不同的一种情况。我不确定如果你在使用 __metaclass__
会给你一个元类冲突错误的情况下使用你的装饰器会发生什么,但我不认为它会令人愉快。
此外,如果涉及任何其他元类,您的方法将导致它们首先运行(可能会修改名称、基数和属性!),然后将它们从类中拉出并使用它来创建一个新的班级。这可能与您使用 __metaclass__
得到的完全不同。
至于__dict__
没有给你一个真正的字典,那只是一个实现细节;我猜是出于性能原因。我怀疑是否有任何规范说(非类)实例的__dict__
必须与类的__dict__
的类型相同(顺便说一句,这也是一个实例;只是元类的一个实例)。类的__dict__
属性是“dictproxy”,它允许您查找属性键,就好像它是dict
但仍然不是dict
。 type
对其第三个参数的类型很挑剔;它想要一个真正的字典,而不仅仅是一个“类似字典”的对象(为破坏鸭子打字而感到羞耻)。这不是 2.x 与 3.x 的事情; Python 3 的行为方式相同,尽管它为您提供了更好的dictproxy
字符串表示。 Python 2.4(这是我现成的最古老的 2.x)也有 dictproxy
对象用于类 __dict__
对象。
【讨论】:
以上是关于使用装饰器设置类的元类的主要内容,如果未能解决你的问题,请参考以下文章