当__setattr__和__getattribute__在python中被覆盖时,无法更新可变实例属性

时间:2018-12-21 02:17:17

标签: python python-3.x getattr setattr

我继承了一堆旧代码,遇到了一些重大问题。我相信这里尝试的想法是使用容器化的缓存(例如redis)来保存某些属性,以在许多进程之间共享。除了这是否是完成任务的最佳方式之外,我还偶然发现了一些令我困惑的东西。如果__getattribute____setattr__方法被如下覆盖,则可变实例属性将无法更新。我希望对self.b所做的更改将反映在print()调用中,但是正如您从输出中看到的那样,仅打印初始的空白字典。单步执行self.b["foo"] = "bar"行表明对__getattribute__的调用已完成,并且从缓存中检索了正确的对象,但没有对__setattr__的调用也未对字典进行任何更新办法。任何人都知道为什么会这样吗?

import dill

_reserved_fields = ["this_is_reserved"]

class Cache:
    _cache = {}

    def get(self, key):
        try:
            return self._cache[key]
        except KeyError:
            return None

    def set(self, key, value):
        self._cache[key] = value

    def exists(self, key):
        return key in self._cache


class Class(object):
    def __init__(self, cache):
        self.__dict__['_cache'] = cache
        self.a = 1
        self.a = 2
        print(self.a)
        self.b = dict()
        self.b["foo"] = "bar"  # these changes aren't set in the cache and seem to disappear
        print(self.b)
        self.this_is_reserved = dict()
        self.this_is_reserved["reserved_field"] = "value"  # this updates fine
        print(self.this_is_reserved)

    def __getattribute__(self, item):
        try:
            return object.__getattribute__(self, item)
        except AttributeError:
            key = "redis-key:" + item
            if not object.__getattribute__(self, "_cache").exists(key):
                raise AttributeError
            else:
                obj = object.__getattribute__(self, "_cache").get(key)
                return dill.loads(obj)

    def __setattr__(self, key, value):
        if key in _reserved_fields:
            self.__dict__[key] = value
        elif key.startswith('__') and key.endswith('__'):
            super().__setattr__(key, value)
        else:
            value = dill.dumps(value)
            key = "redis-key:" + key
            self._cache.set(key, value)

if __name__ == "__main__":
    cache = Cache()
    inst = Class(cache)

    # output
    # 2
    # {}
    # {'reserved_field': 'value'}

2 个答案:

答案 0 :(得分:0)

self.b["foo"] = "bar"行是从自身中获取属性b,其中b被假定为字典,并分配给键{{1} }在上述字典中。只有将 直接分配给b时,"foo"才会被调用,例如__setattr__(在Python 3.5+中用于字典更新)。

无需在存储的对象中添加钩子以触发其self.b = {**self.b, 'foo': 'bar'}及其子对象__setattr__等上的缓存保存,您可以添加一种显式触发缓存更新的方法(这意味着用户代码需要了解缓存),或者避免通过直接分配来更改字段。

对不起,您很幸运;)。

答案 1 :(得分:0)

问题在于您要在不实现对象写回的情况下重新实现搁置模块。

何时:

self.b = dict()

在缓存中将__setattr__存储一个腌制的字典:

然后何时:

self.b["foo"] = "bar"

__getattribute__将字典解开并返回它。然后,Python将该字典的foo键设置为bar。然后它扔掉了字典。