在运行时覆盖__setattr__

时间:2013-05-07 18:26:05

标签: python

我试图覆盖Python类的__setattr__方法,因为每次实例属性更改其值时我都想调用另一个函数。但是,我不希望在__init__方法中出现这种情况,因为在此初始化期间,我设置了一些将在以后使用的属性:

到目前为止,我有这个解决方案,但没有在运行时覆盖__setattr__

class Foo(object):
    def __init__(self, a, host):
        object.__setattr__(self, 'a', a)
        object.__setattr__(self, 'b', b)
        result = self.process(a)
        for key, value in result.items():
            object.__setattr__(self, key, value)

    def __setattr__(self, name, value):
        print(self.b) # Call to a function using self.b
        object.__setattr__(self, name, value)

但是,我想避免这些object.__setattr__(...)并在__setattr__方法的末尾覆盖__init__

class Foo(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b
        result = self.process(a)
        for key, value in result.items():
            setattr(self, key, value)
        # override self.__setattr__ here

    def aux(self, name, value):
        print(self.b)
        object.__setattr__(self, name, value)

我尝试了self.__dict__['__setitem__'] = self.auxobject.__setitem__['__setitem__'] = self.aux,但这些尝试都没有效果。我已阅读this section of the data model reference,但看起来自己的__setattr__的分配有点棘手。

如何才能在__setattr__结束时覆盖__init__,或者至少有一个pythonic解决方案,只在构造函数中以正常方式调用__setattr__

2 个答案:

答案 0 :(得分:23)

不幸的是,没有办法“在init之后覆盖”python特殊方法;作为查找工作方式的副作用。问题的症结在于python实际上并没有查看实例;除了上课;在它开始查找特殊方法之前;所以没有办法让对象的状态影响查找哪个方法。

如果您不喜欢__init__中的特殊行为,则可以重构代码,将特殊知识放在__setattr__中。类似的东西:

class Foo(object):
    __initialized = False
    def __init__(self, a, b):
        try:
            self.a = a
            self.b = b
            # ...
        finally:
            self.__initialized = True

    def __setattr__(self, attr, value):
        if self.__initialzed:
            print(self.b)
        super(Foo, self).__setattr__(attr, value)

编辑:实际上,有一种方法可以改变查找哪种特殊方法,只要你在初始化后更改其类即可。这种方法会让你深入到元类的杂草中,所以没有进一步的解释,这就是它的外观:

class AssignableSetattr(type):
    def __new__(mcls, name, bases, attrs):
        def __setattr__(self, attr, value):
            object.__setattr__(self, attr, value)

        init_attrs = dict(attrs)
        init_attrs['__setattr__'] = __setattr__

        init_cls = super(AssignableSetattr, mcls).__new__(mcls, name, bases, init_attrs)

        real_cls = super(AssignableSetattr, mcls).__new__(mcls, name, (init_cls,), attrs)
        init_cls.__real_cls = real_cls

        return init_cls

    def __call__(cls, *args, **kwargs):
        self = super(AssignableSetattr, cls).__call__(*args, **kwargs)
        print "Created", self
        real_cls = cls.__real_cls
        self.__class__ = real_cls
        return self


class Foo(object):
    __metaclass__ = AssignableSetattr

    def __init__(self, a, b):
        self.a = a
        self.b = b
        for key, value in process(a).items():
            setattr(self, key, value)

    def __setattr__(self, attr, value):
        frob(self.b)
        super(Foo, self).__setattr__(attr, value)


def process(a):
    print "processing"
    return {'c': 3 * a}


def frob(x):
    print "frobbing", x


myfoo = Foo(1, 2)
myfoo.d = myfoo.c + 1

答案 1 :(得分:5)

@ SingleNegationElimination的答案很棒,但它不能用于继承,因为子类的__mro__存储是超类的原始类。灵感来自他的答案,几乎没有变化,

这个想法很简单,在__setattr__之前切换__init__,并在__init__完成后将其恢复。

class CleanSetAttrMeta(type):
    def __call__(cls, *args, **kwargs):
        real_setattr = cls.__setattr__
        cls.__setattr__ = object.__setattr__
        self = super(CleanSetAttrMeta, cls).__call__(*args, **kwargs)
        cls.__setattr__ = real_setattr
        return self


class Foo(object):
    __metaclass__ = CleanSetAttrMeta

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

    def __setattr__(self, key, value):
        print 'after __init__', self.b
        super(Foo, self).__setattr__(key, value)


class Bar(Foo):
    def __init__(self):
        super(Bar, self).__init__()
        self.c = 3

>>> f = Foo()
>>> f.a = 10
after __init__ 2
>>>
>>> b = Bar()
>>> b.c = 30
after __init__ 2