更新
如果有计划将 version 添加到列表并公开,我会觉得现在提出一个不兼容的VersionedList很尴尬。我只是实现我需要的最低限度并且过去了。
以下原帖
事实证明,很多时候我想要一个不可变的列表, VersionedList 几乎同样有效(有时甚至更好)。
版本化列表的含义是:
对实例中的实例或元素进行的任何更改都会导致更新instance.version()
。因此,如果alist
是正常列表:
a = VersionedList(alist)
a_version = a.version()
change(a)
assert a_version != a.version()
reverse_last_change(a)
如果列表是可清除的,则hash()将实现上述目标并满足以下动机中确定的所有需求。我们需要定义' version()'以某种方式没有所有与' hash()'相同的问题。
如果在初始化时极不可能发生两个列表中的相同数据,那么我们就没有理由测试深度相等性。 From(https://docs.python.org/3.5/reference/datamodel.html#object.hash)唯一必需的属性是比较相等的对象具有相同的哈希值。如果我们不将此要求强加于版本()',则可能是' version()'不会有所有相同的问题,使列表不可用。因此,与哈希不同,相同的内容不会表示相同的版本
#contents of 'a' are now identical to original, but...
assert a_version != a.version()
b = VersionedList(alist)
c = VersionedList(alist)
assert b.version() != c.version()
对于VersionList,如果任何修改__get__
的结果的尝试自动导致副本而不是修改底层实现数据,那将是很好的。我认为唯一的另一种选择是让__get__
总是返回元素的副本,这对于我能想到的所有用例来说都是非常低效的。我认为我们需要将元素限制为不可变对象(深度不可变,例如:使用列表元素排除元组)。我可以想出三种方法来实现这个目标:
__init__
,__set__
等,以遍历输入以深入检查可变子元素。 (贵,有什么方法可以避免这种情况?) deeply_immutable
属性。 (这对我所有的用例来说都很容易) 动机:
如果我正在分析数据集,我经常需要执行多个返回大数据集的步骤(注意:由于数据集是有序,最好用List而不是集合表示)。
如果在几个步骤结束时(例如5),我发现我需要执行不同的分析(例如:返回步骤4),我想知道步骤3中的数据集不是偶然的被改变了。这样我就可以从第4步开始,而不是重复步骤1-3。
我有依赖并返回数组值对象的函数(控制点,一阶导数,二阶导数,偏移,轮廓等)(在线性代数意义上)。基地阵列'是knots
。
control-points()
取决于: knots
,algorithm_enum
first-derivative()
取决于: control-points()
,knots
offset()
取决于: first-derivative()
,control-points()
,knots
,offset_distance
outline()
取决于: offset()
,end_type_enum
如果offset_distance
发生变化,我想避免重新计算一阶导数()和控制点()。为避免重新计算,我需要知道没有任何意外更改结果的数组'。
如果打结'更改,我需要重新计算所有内容,而不是依赖于之前生成的数组'。
为实现这一目标,我需要knots
以及所有'数组值'对象可以是VersionedList。
仅供参考:我曾希望利用像numpy.ndarray这样的高效课程。在我的大多数用例中,元素在逻辑上都具有结构。必须精神上跟踪索引的多维度意味着使用ndarray实现和调试算法要困难许多倍。基于namedtuples的命名元组列表的实现证明更具可持续性。
答案 0 :(得分:1)
3.6中的私人词汇
在Python 3.6中,字典现在是私有的(PEP 509)和紧凑的(issue 27350),它们分别跟踪版本和保留顺序。当使用CPython 3.6实现时,这些功能目前是正确的。尽管面临挑战,Jake VanderPlas在他的blog post演示了在普通Python中从CPython中暴露这个版本控制功能的详细演示。我们可以用他的方法:
实施例
import numpy as np
d = {"a": np.array([1,2,3]),
"c": np.array([1,2,3]),
"b": np.array([8,9,10]),
}
for i in range(3):
print(d.get_version()) # monkey-patch
# 524938
# 524938
# 524938
请注意,在更新字典之前,版本号不会更改,如下所示:
d.update({"c": np.array([10, 11, 12])})
d.get_version()
# 534448
此外,保留了插入顺序(以下是在Python 3.5和3.6的重新启动的会话中测试过的):
list(d.keys())
# ['a', 'c', 'b']
您可以利用这种新的字典行为,从而避免实现新的数据类型。
<强>详情
对于那些感兴趣的人,后者get_version()
是任何字典的猴子修补方法,使用从Jake VanderPlas博客文章派生的以下修改代码在Python 3.6中实现。此代码在调用get_version().
import types
import ctypes
import sys
assert (3, 6) <= sys.version_info < (3, 7) # valid only in Python 3.6
py_ssize_t = ctypes.c_ssize_t
# Emulate the PyObjectStruct from CPython
class PyObjectStruct(ctypes.Structure):
_fields_ = [('ob_refcnt', py_ssize_t),
('ob_type', ctypes.c_void_p)]
# Create a DictStruct class to wrap existing dictionaries
class DictStruct(PyObjectStruct):
_fields_ = [("ma_used", py_ssize_t),
("ma_version_tag", ctypes.c_uint64),
("ma_keys", ctypes.c_void_p),
("ma_values", ctypes.c_void_p),
]
def __repr__(self):
return (f"DictStruct(size={self.ma_used}, "
f"refcount={self.ob_refcnt}, "
f"version={self.ma_version_tag})")
@classmethod
def wrap(cls, obj):
assert isinstance(obj, dict)
return cls.from_address(id(obj))
assert object.__basicsize__ == ctypes.sizeof(PyObjectStruct)
assert dict.__basicsize__ == ctypes.sizeof(DictStruct)
# Code for monkey-patching existing dictionaries
class MappingProxyStruct(PyObjectStruct):
_fields_ = [("mapping", ctypes.POINTER(DictStruct))]
@classmethod
def wrap(cls, D):
assert isinstance(D, types.MappingProxyType)
return cls.from_address(id(D))
assert types.MappingProxyType.__basicsize__ == ctypes.sizeof(MappingProxyStruct)
def mappingproxy_setitem(obj, key, val):
"""Set an item in a read-only mapping proxy"""
proxy = MappingProxyStruct.wrap(obj)
ctypes.pythonapi.PyDict_SetItem(proxy.mapping,
ctypes.py_object(key),
ctypes.py_object(val))
mappingproxy_setitem(dict.__dict__,
'get_version',
lambda self: DictStruct.wrap(self).ma_version_tag)