为什么我的元类表现不像预期的那样

时间:2012-12-28 15:15:56

标签: python metaclass

好吧所以我开始乱用voodoo和带有元类的Python的黑暗面,我想在我进一步修改之前得到一个简单的例子,为什么在执行这段代码时不打印print语句,< / p>

class Versioned(type):
    def __new__(cls, name, bases, dct):
        return type.__new__(cls, name, bases, dct)

    def __setattr__(cls, key, value):
        print "Setting attr %s to %s" % (key, value)
        return object.__setattr__(cls, key, value)

class Test(object):
    __metaclass__ = Versioned

    def __init__(self):
        self.a = 1
        self.b = 2

a = Test()
a.b = 31
print dir(a)

3 个答案:

答案 0 :(得分:2)

要理解的是,元类本身不是 - 它是一个可调用的,它返回一个类 - 一个类工厂< / em>的。它的作用与type(__name__, __bases__, __dict__)相同。

>>> type('myclass', (), {})
<class '__main__.myclass'>

定义__metaclass__时,您只是overriding the default class factory for that specific class or module。例如,这是一个元类:

def setattr_logging_class(name, bases, dict_):
    """I am a metaclass"""
    def __setattr__(self, k, v):
        print "{} set attribute {} to {}".format(self, k, v)
        super(self.__class__, self).__setattr__(k, v)
    dict_['__setattr__'] = __setattr__
    cls = type(name, bases, dict_)
    return cls

class MyClass(object):
    __metaclass__ = setattr_logging_class
    def __init__(self):
        self.a = 1

obj = MyClass()
obj.b = 2

print obj.__dict__

最重要的是,元类不参与创建的类的方法解析(除非您更改bases)。这就是您的Versioned.__setattr__个实例无法看到Test的原因。所有Versioned所做的都是返回一个新类(类型为Versioned而不是类型type),具有相同的namebasesdict_ Python运行时解析出class Test(object):块。

类本身可以调用(通过他们的__new__方法)。 (__new__适用于__call__实例的类。)

class MyClass(object):
    def __new__(cls_self, *args, **kwargs):
        print "I am {} called with {} and {}".format(cls_self, args, kwargs)
        return None

myobj = MyClass(1,2,3,a=4,b=5,c=6)
print myobj # == None

由于类是可调用的,因此可以将类用作元类。实际上,type是一个类。虽然,你也可以使用__call__方法的实例!这两个例子是相似的,没有任何你可能想要做的事情,这些都无法做到办法。 (事实上​​,使用对象通常更直接。)

class MetaClass(type):
    def __new__(cls, name, bases, dict_):
        print "I am {} called with {}, {}, {}".format(cls, name, bases, dict_)
        return type.__new__(cls, name, bases, dict_)

class MetaObject(object):
    def __call__(self, name, bases, dict_):
        print "I am {} called with {}, {}, {}".format(self, name, bases, dict_)
        return type(name, bases, dict_)


class MyClass(object):
    __metaclass__ = MetaClass


class MyClass2(object):
    __metaclass__ = MetaObject()

请注意,除了元类之外,Python还有许多其他的元编程方法。特别是,Python&gt; = 2.6增加了对类装饰器的支持,它涵盖了具有更简单接口的元类的大多数用例。 (而不是自己创建类,您将获得一个已修改的已创建的类对象。)

您可以在this stackoverflow answer I recently wrote查看多种元​​编程方法的调查。最初的问题是“我如何制作内置的Python容器类型线程安全?”

答案 1 :(得分:1)

我从未真正使用过元类,但我认为你将继承与元类混合在一起。元类用于构建实例,但它们不会向您的实例隐式添加任何内容。没有理由在__setattr__中导入Test方法。要明确地添加它,您需要修改Test dict(dct)。像dct["__setattr__"] = cls.__setattr__这样的东西。但是,它不起作用,我不明白为什么。我发现让它发挥作用的唯一方法是:

class Versioned(type):
    def __new__(cls, name, bases, dct):
        print("BLAH")
        def __setattr__(cls, key, value):
            print("Setting attr %s to %s" % (key, value))
            return object.__setattr__(cls, key, value)
        dct["__setattr__"] = __setattr__
        return type.__new__(cls, name, bases, dct)

我希望这可以提供帮助: - )

最后,这个link可以帮助你理解python中的元类。

答案 2 :(得分:1)

您正在为类本身定义__setattr__,而不是为其实例定义Test.key = value 。这将导致print语句执行:

{{1}}