在运行时覆盖 __setattr__

6 浏览
0 Comments

在运行时覆盖 __setattr__

我正在尝试覆盖Python类的__setattr__方法,因为我想在每次实例属性更改值时调用另一个函数。然而,在__init__方法中,我不想要这种行为,因为在初始化期间我设置了一些稍后要使用的属性:\n到目前为止,我有这个解决方案,没有在运行时覆盖__setattr__:\n

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) # 使用self.b调用函数
        object.__setattr__(self, name, value)

\n然而,我想避免这些object.__setattr__(...),并在__init__方法的末尾覆盖__setattr__:\n

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)
        # 在此处覆盖self.__setattr__
    def aux(self, name, value):
        print(self.b)
        object.__setattr__(self, name, value)

\n我尝试了self.__dict__[\'__setitem__\'] = self.auxobject.__setitem__[\'__setitem__\'] = self.aux,但是这些尝试都没有效果。我阅读了数据模型参考手册的这一部分,但似乎分配自己的__setattr__有点棘手。\n如何在__init__的末尾覆盖__setattr__,或者至少有一个Python风格的解决方案,在构造函数中只调用普通方式的__setattr__

0
0 Comments

问题的出现原因:

上述代码中,问题出现在子类Bar的初始化方法__init__中。在调用super(Bar, self).__init__()时,会执行父类Foo的__init__方法。而在Foo的__init__方法中,调用了父类object的__init__方法,并在之后设置了self.a和self.b的值。然而,在设置self.a和self.b的值时,触发了父类Foo的__setattr__方法。在这个方法中,打印了self.b的值。然而,由于子类Bar并没有设置self.b的值,所以打印的结果是2,而不是期望的None。

解决方法:

为了解决上述问题,可以通过重写__setattr__方法来实现。在重写的__setattr__方法中,首先调用super(Foo, self).__setattr__(key, value)来执行父类Foo的__setattr__方法,然后再打印self.b的值。

完整代码如下:

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):
        super(Foo, self).__setattr__(key, value)
        print 'after __init__', self.b
class Bar(Foo):
    def __init__(self):
        super(Bar, self).__init__()
        self.c = 3
f = Foo()
f.a = 10
b = Bar()
b.c = 30

运行上述代码,输出结果如下:

after __init__ 2
after __init__ 2

通过重写__setattr__方法后,解决了子类Bar中调用父类Foo的__init__方法时,打印self.b的值不正确的问题。

0
0 Comments

在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

哇!真是太棒了。我偶然发现了这个方法,差点忽视了它,但事实证明它正是我解决类似问题所需的。这个答案中有一些真正的Python魔法。很抱歉我不能点赞100次 :p

0