2012-06-18 57 views
5

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 

它看ms表明元类魔术应用于B,但B没有__metaclass__属性。是上述方法来元类应用类定义一个合理的方式,即使我definiting并重新definiting类,或者我会过得更好只是写

class B(object): 
    __metaclass__ = MyMetaClass 
    pass 

我相信有这两者之间的一些差异方法。


*注意,在链接的问题,MyMetaClass(A.__name__, A.__bases__, A.__dict__)原来的答案,返回TypeError

TypeError: type() argument 3 must be a dict, not dict_proxy

看来的A(类定义)的__dict__属性有一个类型dict_proxy,而__dict__属性的类型实例A的类型为dict。为什么是这样?这是一个Python 2.x与3.x的区别吗?

回答

1

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

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

事实上,正常类是元类type的所有实例,因此如果元类总是将__metaclass__属性上它的实例,然后类将有一个__metaclass__属性(大多设定为type)。


我不会使用你的装饰器方法。它掩盖了元类涉及(以及哪一个)的事实,仍然是一行样板,并且仅仅从这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作为其元类而attrs不包含__metaclass__那么默认元类是祖先的元类而不是type。这是我希望你的装饰“解决方案”与直接使用__metaclass__不同的一种情况。我不确定如果在使用__metaclass__会给你一个元类冲突错误的情况下使用装饰器会发生什么情况,但我不希望它会令人愉快。另外,如果还有其他元类参与,你的方法会导致它们首先运行(可能会修改名称,基础和属性!),然后将它们从类中拉出来并使用它创建新班。这可能会与您使用__metaclass__的情况完全不同。

至于__dict__没有给你一个真正的字典,这只是一个实现细节;我猜是出于性能原因。我怀疑是否有任何规范说__dict__(非类)实例的类型必须是类的__dict__(它也是一个实例btw;只是元类的一个实例)。 __dict__属性的类是一个“dictproxy”,它允许您查找属性键,就好像它是一个dict但仍然不是dicttype对第三个参数的类型是挑剔的;它需要一个真正的字典,而不仅仅是一个“类字典”的对象(因为糟糕的鸭子打字而感到羞耻)。这不是一个2.x和3.x的东西; Python 3的行为方式相同,但它给了你一个更好的字符串表示形式dictproxy。 Python 2.4(这是我现有的最老的2.x)也有dictproxy对象用于__dict__类对象。

1

我对你的问题总结:“我尝试了一个新的棘手方式去做一件事,但它并没有起作用,我应该用简单的方式来代替吗?”

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

+0

我只是想知道是否有一个装饰器等效于在类定义中设置__metaclass__属性。我想我的问题的另一部分是我的'装饰'似乎*工作,但我注意到,装饰类没有'__metaclass__'属性,所以这两种方法之间显然是有区别的;为什么是这样? – Chris

3

不可否认,我晚了一点。不过,我倒是值得补充。

这是完全可行的。这就是说,还有很多其他方法可以实现相同的目标。但是,装饰解决方案尤其允许延迟评估(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