Невозможно обновить атрибуты изменяемого экземпляра, когда __setattr__ и __getattribute__ переопределены в python
Я унаследовал кучу унаследованного кода и столкнулся с проблемой. Я полагаю, что идея, которая здесь была предпринята, состояла в том, чтобы использовать контейнерный кэш, такой как 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 ответа
Линия self.b["foo"] = "bar"
получает атрибут b
от себя, где b
Предполагается, что это словарь, и присвоение ключа "foo"
в указанном словаре. __setattr__
будет вызываться только в том случае, если вы назначите непосредственно b, например, self.b = {**self.b, 'foo': 'bar'}
(в Python 3.5+ для обновления словаря).
Без добавления хуков в ваших хранимых объектах, чтобы вызвать кэш-сохранение их __setattr__
и их подобъекты __settattr__
и т. д., вы можете добавить способ явного запуска обновления кэша (то есть пользовательский код должен знать о кэше) или избежать изменения полей, за исключением прямого назначения.
Извините за вашу удачу;).
Проблема в том, что вы повторно внедряете модуль полки без реализации обратной записи объекта.
Когда:
self.b = dict()
происходит __setattr__
сохраняет маринованный словарь в кеше:
Тогда, когда:
self.b["foo"] = "bar"
__getattribute__
Разбирает словарь и возвращает его. Затем Python устанавливает ключ foo этого словаря в bar. Затем он выбрасывает словарь.