我正在尝试编写一个函数来确定在程序执行期间某个对象是否已被修改。
我不想复制对象,因为这会占用大量内存。
我的对象是一个数据类,有一些数据类列表,其中可能有嵌套的数据类,但在底层你只能找到原始变量(str、ints、bool、...)
由于这些对象需要修改我不能使用
frozen=True
。到目前为止,我提出的是hash(str(self)) == PreviousHash
,但随着数据量的增加,速度开始大大降低。
你会怎么做才能获得像这样的数据类实例的“散列”,而不必首先缓慢地转换为字符串?
这不是万无一失的解决方案,但您可以使用代理类递归地包装您的数据类,并在任何修改时设置一个标志。通过添加间接和动态行为,它会在一定程度上影响性能,但至少它不会随着数据类变大而变得更糟。
这是它的初稿。
class Proxy:
"""
Wraps an object to keep track of modifications, including to its children.
"""
def __init__(self, obj, modified_flag=None):
# Must use `super().__setattr__` to avoid recursing on itself.
super().__setattr__('_obj', obj)
super().__setattr__('_modified_flag', modified_flag or [False])
@property
def is_modified(self):
""" Returns True if any object in this tree has been modified. """
return self._modified_flag[0]
def _set_modified(self):
self._modified_flag[0] = True
def _wrap_subvalue(self, value):
"""
Given an attribute or index value, decides if it should be returned as-is
(e.g. primitive types), wrapped in another Proxy (e.g. substructures), or
if it's a modifying function call and the respective flag should be set.
"""
if isinstance(value, (int, str, float, bool, bytes)):
return value
elif callable(value):
# List of functions that modify the object.
if value.__qualname__ in ('list.append', 'list.pop', 'list.clear', 'list.extend', 'list.insert', 'list.remove', 'list.sort', 'list.reverse', 'dict.popitem', 'dict.update', 'dict.pop', 'dict.clear'):
self._set_modified()
return value
else:
return Proxy(obj=value, modified_flag=self._modified_flag)
def __getattr__(self, name):
return self._wrap_subvalue(getattr(self._obj, name))
def __setattr__(self, name, value):
self._set_modified()
setattr(self._obj, name, value)
def __getitem__(self, index):
return self._wrap_subvalue(self._obj[index])
def __setitem__(self, index, value):
self._set_modified()
self._obj[index] = value
你会像这样使用它:
from dataclasses import dataclass
@dataclass
class Child:
value: float
@dataclass
class Parent:
"""Class for keeping track of an item in inventory."""
name: str
children: [Child]
def bogus_operation(self) -> float:
return sum(child.value for child in self.children) / len(self.children)
raw_parent = Parent('parent name', [Child(value=5), Child(value=4)])
parent = Proxy(raw_parent) # Proxy here!
parent.bogus_operation()
print(parent.is_modified)
parent.children[0].value = 2
parent.children.append(Child(1))
print(parent.is_modified)
一个选项是只设置
unsafe_hash=True
:
https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass
这将强制生成一个
__hash__
方法,类似于您使用不可变数据类获得的方法。
请注意,如果你这样做,你需要格外小心,不要在哈希被假定为“安全”(即静态)的上下文中意外使用你的数据类,因为可变对象的哈希本身可能会发生变化.例如,使您的数据类可散列将允许您将其用作字典键,但是当您修改键时字典将具有未定义的行为,因为其内部哈希表将在其不知情的情况下失效。
我发现了一些有用的东西,不像 hash(str(instance)) 那样慢,但随着数据的增加仍然会变慢(这在我的容忍范围内)。
注意:对于非常小的实例来说速度较慢,但对于非常大的实例来说要快得多。
我不会接受它,因为它没有回答我的具体问题,但如果这对某人有用,我会把它贴在这里。
from dataclasses import dataclass, field
import os
from pathlib import Path
import pickle
@dataclass()
class Investigation:
Path: str
PreviousHash: int = field(default=0)
Users: list[User] = field(default_factory=list)
Tasks: list[Task] = field(default_factory=list)
Nodes: list[Node] = field(default_factory=list)
Edges: list[Edge] = field(default_factory=list)
@property
def Name(self) -> str:
return Path(self.Path).stem
@property
def Directory(self) -> str:
return os.path.dirname(self.Path)
def Snapshot(self):
self.PreviousHash = hash(pickle.dumps(self))
@property
def HasChanges(self) -> bool:
return hash(pickle.dumps(self)) != self.PreviousHash
def __getstate__(self):
state = self.__dict__.copy()
state["PreviousHash"] = 0
return state
def __setstate__(self, state):
self.__dict__.update(state)
self.PreviousHash = hash(pickle.dumps(self))
这个有用吗?
def has_object_been_modified(obj):
"""Checks if an object has been modified during program execution.
Returns True if the object has been modified, False otherwise.
"""
original_hash = hash(obj)
current_hash = hash(obj)
if current_hash != original_hash:
return True
return False
my_list = [1, 2, 3, 4]
has_been_modified = has_object_been_modified(my_list)
if has_been_modified:
print("The list has been modified!")
else:
print("The list has not been modified.")