Пытаюсь сделать объект - копию обычного словаря но с дополнительной функцией авто-сохранения на диск.
Сохранять можно при записи, например в обработчике __setitem__ но у этого варианта есть проблема с тем что в этот обработчик попадает только верхний уровень
d[1] = 2 # сработает
d[1][2] = 3 # не сработает, тут сработает обработчик объекта d[1].__setitem__ который не имеет ничего общего с d.__setitem__ а d просто не заметит что у него внутри что то изменилось
Что бы замечать изменения глубже 1 уровня попробовал использовать постоянно работающий поток который делает хеш содержимого словаря и сверяет с предыдущим, так всё работает но есть проблема с завершением программы.
Программа не завершается если вручную не остановить этот поток. Вручную вызвать .stop() или использовать with. Я хочу что бы поток принудительно останавливался когда объект выходит из зоны видимости, завершается программа или функция в которой он создан.
#!/usr/bin/env python3
import pickle
import time
import threading
import sqlitedict
class PersistDict:
def __init__(self, filename = 'test.pickle', timer = 2):
self.filename = filename
self.data = {}
self.last_hash = None
self.timer = timer
self.lock = threading.Lock()
self.load()
self._thread = threading.Thread(target=self.run)
self.running = True
self._thread.start()
def __del__(self):
"""да я вкурсе что это не деструктор в том смысле что у c++
вопрос тут как с этим быть, если хочется что бы объект удалялся
(останавливался поток) при выходе из области видимости?
без контекстного менеджера и прямого вызова .stop()
"""
self.stop()
def stop(self):
self.running = False
self._thread.join()
self.check_for_changes()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.stop()
def __getitem__(self, key):
return self.data[key]
def __setitem__(self, key, value):
self.data[key] = value
def __delitem__(self, key):
del self.data[key]
def __contains__(self, key):
return key in self.data
def __reversed__(self):
return reversed(self.data)
def __eq__(self, other):
return self.data == other
def __hash__(self):
return hash(pickle.dumps(self.data))
def __ne__(self, other):
return self.data != other
def __len__(self):
return len(self.data)
def __iter__(self):
return iter(self.data)
def clear(self):
self.data.clear()
# def copy(self):
# return PersistDict(self.filename, self.data.copy())
@classmethod
def fromkeys(cls, keys, value=None):
return cls({key: value for key in keys})
def get(self, key, default=None):
return self.data.get(key, default)
def items(self):
return self.data.items()
def keys(self):
return self.data.keys()
def pop(self, key, default=None):
value = self.data.pop(key, default)
return value
def popitem(self):
key, value = self.data.popitem()
return key, value
def setdefault(self, key, default=None):
value = self.data.setdefault(key, default)
return value
def update(self, other):
self.data.update(other)
def values(self):
return self.data.values()
def load(self):
try:
with open(self.filename, 'rb') as f:
self.data = pickle.load(f)
self.last_hash = hash(pickle.dumps(self.data))
except FileNotFoundError:
pass
def save(self):
with self.lock:
with open(self.filename, 'wb') as f:
print('saving')
pickle.dump(self.data, f)
def check_for_changes(self):
new_hash = hash(pickle.dumps(self.data))
if new_hash != self.last_hash:
self.save()
self.last_hash = new_hash
def run(self):
while self.running:
time.sleep(self.timer)
self.check_for_changes()
print('running')
if __name__ == '__main__':
# этот вариант работает как задумано но требут контекстного менеджера
# with PersistDict() as d:
# for x in range(10):
# d[x] = x+1
# time.sleep(1)
# print(d[x])
# этот вариант работает как задумано но требует ручного вызова функции stop
# d = PersistDict()
# for x in range(10):
# d[x] = x+1
# time.sleep(1)
# print(d[x])
# d.stop()
# в этом варианте программа не завершается
# объект не уничтожается, поток не останавливается,
# хотя vscode показывает что потоков больше нет
# d = PersistDict()
# for x in range(10):
# d[x] = x+1
# time.sleep(1)
# print(d[x])
# тот же результат что и в предыдущем варианте
# попытка удалить объект вручную приводит к.. ничему. это вообще не работает,
# функция __del__ не вызывается(ставил туда точку останова и принты),
# но и ошибок никаких не порождает, до финиша доходит в любом случае
d = PersistDict()
for x in range(10):
d[x] = x+1
time.sleep(1)
print(d[x])
del d
print('finished')