6

按照这个答案,似乎可以在使用以下*定义类后更改类的元类:

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

似乎(类定义)的__dict__属性具有类型,而实例的属性的类型具有类型。为什么是这样?这是 Python 2.x 与 3.x 的区别吗?Adict_proxy__dict__Adict

4

3 回答 3

6

诚然,我参加聚会有点晚了。但是,我认为这是值得添加的。

这是完全可行的。话虽如此,还有很多其他方法可以实现相同的目标。但是,装饰解决方案尤其允许延迟评估 ( obj = dec(obj)),而__metaclass__在类内部使用则不允许。在典型的装饰风格中,我的解决方案如下。

如果您只是在不更改字典或复制其属性的情况下构建类,您可能会遇到一件棘手的事情。该类之前(装饰之前)具有的任何属性似乎都将丢失。所以,复制这些然后调整它们是绝对必要的,就像我在我的解决方案中所做的那样。

就个人而言,我喜欢能够跟踪对象是如何被包裹的。所以,我添加了__wrapped__属性,这不是绝对必要的。它也使它更像functools.wrapsPython 3 中的类。但是,它对内省很有帮助。此外,__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
于 2014-07-25T13:33:55.340 回答
2

我对您的问题的总结:“我尝试了一种新的棘手方法来做一件事,但效果不佳。我应该改用简单的方法吗?”

是的,你应该用简单的方法来做。你还没有说为什么你有兴趣发明一种新的方法来做到这一点。

于 2012-06-18T21:53:22.090 回答
1

该类没有__metaclass__设置属性...因为您从未设置它!

使用哪个元类通常__metaclass__类块中设置的名称确定。该__metaclass__属性不是由元类设置的。因此,如果您直接调用元类而不是设置__metaclass__并让 Python 找出它,则不会__metaclass__设置任何属性。

事实上,普通类都是元类的实例type,所以如果元类总是__metaclass__在它的实例上设置属性,那么每个类都会有一个__metaclass__属性(大多数都设置为type)。


我不会使用您的装饰器方法。它掩盖了一个事实,即涉及元类(以及哪个),仍然是样板代码的一行,并且从 3 个定义特征创建一个类(name, bases, attributes)只是为了将这 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作为其元类并且attrs不包含__metaclass__则默认metaclass 是祖先的元类,而不是type. 这是我希望您的装饰器“解决方案”与直接使用不同的一种情况__metaclass__。我不确定如果在使用__metaclass__会给你一个元类冲突错误的情况下使用你的装饰器会发生什么,但我不希望它是令人愉快的。

此外,如果涉及任何其他元类,您的方法将导致它们首先运行(可能会修改名称、基数和属性!)然后将它们从类中拉出并使用它来创建一个新类。这可能与您使用__metaclass__的 .

至于__dict__不给你一个真正的字典,那只是一个实现细节;我猜是出于性能原因。我怀疑是否有任何规范说__dict__(非类)实例的类型必须与类的类型相同__dict__(顺便说一句,这也是一个实例;只是元类的一个实例)。类的__dict__属性是“dictproxy”,它允许您查找属性键,就好像它是 adict但仍然不是dict. type对其第三个参数的类型很挑剔;它想要一个真正的字典,而不仅仅是一个“类似字典”的对象(为破坏鸭子打字而感到羞耻)。这不是 2.x 与 3.x 的事情;Python 3 的行为方式相同,尽管它为您提供了更好的字符串表示dictproxy. Python 2.4(这是我现成的最古老的 2.x)也有dictproxy用于类对象的__dict__对象。

于 2012-06-19T02:53:08.640 回答