【问题标题】: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 的区别吗?

【问题讨论】:

    标签: python decorator metaclass


    【解决方案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
    

    【讨论】:

    【解决方案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__ 对象。

    【讨论】:

      猜你喜欢
      • 2011-07-17
      • 2010-12-19
      • 1970-01-01
      • 2014-01-14
      • 2021-07-16
      • 2019-05-02
      • 1970-01-01
      • 2019-08-08
      • 2023-03-02
      相关资源
      最近更新 更多