使用装饰器设置类的元类

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 但仍然不是dicttype 对其第三个参数的类型很挑剔;它想要一个真正的字典,而不仅仅是一个“类似字典”的对象(为破坏鸭子打字而感到羞耻)。这不是 2.x 与 3.x 的事情; Python 3 的行为方式相同,尽管它为您提供了更好的dictproxy 字符串表示。 Python 2.4(这是我现成的最古老的 2.x)也有 dictproxy 对象用于类 __dict__ 对象。

【讨论】:

以上是关于使用装饰器设置类的元类的主要内容,如果未能解决你的问题,请参考以下文章

派生类的类/元类方法装饰器

Python 元类与类装饰器

我应该使用元类、类装饰器还是重写 __new__ 方法?

Python - 元类装饰器 - 如何使用 @classmethod

Python之路:描述符,类装饰器,元类

Python 中的纯静态类 - 使用元类、类装饰器还是其他东西?