我可以使用Python的产权制度只读列表?
我创建了具有列表作为成员的Python类。在内部,我想它的每一个列表中被修改的时间做一些事情。如果这是C ++,我创建getter和setter方法,让我做我的簿记每当setter方法被调用,而且我有吸气返回const
引用,所以编译器将在我大喊如果我试图不要通过吸气修改列表。在Python中,我们有产权制度,使写作香草getter和setter方法对每个数据成员(谢天谢地)不再是必要的。然而,考虑下面的脚本:
def main():
foo = Foo()
print('foo.myList:', foo.myList)
# Here, I'm modifying the list without doing any bookkeeping.
foo.myList.append(4)
print('foo.myList:', foo.myList)
# Here, I'm modifying my "read-only" list.
foo.readOnlyList.append(8)
print('foo.readOnlyList:', foo.readOnlyList)
class Foo:
def __init__(self):
self._myList = [1, 2, 3]
self._readOnlyList = [5, 6, 7]
@property
def myList(self):
return self._myList
@myList.setter
def myList(self, rhs):
print("Insert bookkeeping here")
self._myList = rhs
@property
def readOnlyList(self):
return self._readOnlyList
if __name__ == '__main__':
main()
输出:
foo.myList: [1, 2, 3]
# Note there's no "Insert bookkeeping here" message.
foo.myList: [1, 2, 3, 4]
foo.readOnlyList: [5, 6, 7, 8]
这说明,如果没有在Python const
的概念让我使用append()
方法修改我的名单,尽管我做了它的属性。这可以绕过我的代理记账机构(_myList
),或者它可以用来修改一个可能喜欢被只读(_readOnlyList
)名单。
一个解决办法是在getter方法返回列表中的深层副本(即return self._myList[:]
)。这可能意味着大量额外的复制,如果列表很大或复印在一个内部循环完成。 (但是,过早的优化是所有罪恶的根源,反正。)此外,虽然深拷贝将阻止簿记机制被绕过,如果有人打电话.myList.append()
,其变化会被丢弃,这可能会产生一些痛苦的调试。这将是很好,如果一个例外中提出,这样他们就会知道,他们正在对这个类的设计。
对于此最后一个问题的解决将是不使用产权制度,使“正常”的getter和setter方法:
def myList(self):
# No property decorator.
return self._myList[:]
def setMyList(self, myList):
print('Insert bookkeeping here')
self._myList = myList
如果用户试图打电话append()
,它看起来像foo.myList().append(8)
,而那些额外的括号将线索他们,他们可能会得到一个副本,而不是内部列表的数据参考。关于这个消极的东西是,它是一种非Python化的写getter和setter这样,如果类有其他列表的成员,我将不得不编写对那些(EWW)getter和setter方法,或使界面不一致。 (我想稍微不一致的接口可能是最万恶的。)
有另一种解决方案,我很想念?一个可以使用Pyton的产权制度只读列表?
两个主要的建议似乎不是使用一个元组作为一个只读列表,或子类列表。我喜欢这两个办法的。从吸气剂返回的元组,或者在第一位置使用一个元组,防止一个从使用+ =运算符,它可以是一个有用的操作,并且还通过调用设定器触发簿记机制。然而,返回一个元组是一个单行的变化,这是很好的,如果你想防守编程,但法官认为加入一个整体的其他类脚本可能会不必要地复杂化。 (它可以是有时好得简约,假设你是不会需要它。)
这里是说明了这两种方法,任何人通过谷歌找到这个剧本的更新版本。
import collections
def main():
foo = Foo()
print('foo.myList:', foo.myList)
try:
foo.myList.append(4)
except RuntimeError:
print('Appending prevented.')
# Note that this triggers the bookkeeping, as we would like.
foo.myList += [3.14]
print('foo.myList:', foo.myList)
try:
foo.readOnlySequence.append(8)
except AttributeError:
print('Appending prevented.')
print('foo.readOnlySequence:', foo.readOnlySequence)
class UnappendableList(collections.UserList):
def __init__(self, *args, **kwargs):
data = kwargs.pop('data')
super().__init__(self, *args, **kwargs)
self.data = data
def append(self, item):
raise RuntimeError('No appending allowed.')
class Foo:
def __init__(self):
self._myList = [1, 2, 3]
self._readOnlySequence = [5, 6, 7]
@property
def myList(self):
return UnappendableList(data=self._myList)
@myList.setter
def myList(self, rhs):
print('Insert bookkeeping here')
self._myList = rhs
@property
def readOnlySequence(self):
# or just use a tuple in the first place
return tuple(self._readOnlySequence)
if __name__ == '__main__':
main()
输出:
foo.myList: [1, 2, 3]
Appending prevented.
Insert bookkeeping here
foo.myList: [1, 2, 3, 3.14]
Appending prevented.
foo.readOnlySequence: (5, 6, 7)
感谢大家。
你可以有方法返回在你的原始列表的包装 - collections.Sequence
可能是帮助写它。或者,你可以返回一个tuple
- 复制列表到一个元组通常是可以忽略的开销。
虽然最终,如果用户想改变基础列表,他们可以而且也确实没有什么可以做,以阻止他们。 (毕竟,他们可以直接访问self._myList
如果他们想的话)。
我认为Python的方式做这样的事情是记录他们不应该改变的列表,如果这样做,那么这是他们的错,当他们的程序崩溃和烧伤。
为什么是最好元组的列表?元组是,对于大多数的意图和目的,“不可改变的名单” - 这样自然就会为只读,不能直接设置或修改的对象采取行动。在这一点上,人们只需不写说二传手该属性。
>>> class A(object):
... def __init__(self, list_data, tuple_data):
... self._list = list(list_data)
... self._tuple = tuple(tuple_data)
... @property
... def list(self):
... return self._list
... @list.setter
... def list(self, new_v):
... self._list.append(new_v)
... @property
... def tuple(self):
... return self._tuple
...
>>> Stick = A((1, 2, 3), (4, 5, 6))
>>> Stick.list
[1, 2, 3]
>>> Stick.tuple
(4, 5, 6)
>>> Stick.list = 4 ##this feels like a weird way to 'cover-up' list.append, but w/e
>>> Stick.list = "Hey"
>>> Stick.list
[1, 2, 3, 4, 'Hey']
>>> Stick.tuple = 4
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
>>>
返回一个元组或子类列表返回的提出的解决方案,似乎是不错的解决方案,但我不知道是否会再容易不过了子类的装饰呢?不知道它,这可能是一个愚蠢的想法:
safe_property
防止意外保护发出混合信号API(内部不可变的属性是“受保护”反对所有操作,而对可变属性,一些操作仍与正常property
内置允许)class FrozenList(list):
def _immutable(self, *args, **kws):
raise TypeError('cannot change object - object is immutable')
pop = _immutable
remove = _immutable
append = _immutable
clear = _immutable
extend = _immutable
insert = _immutable
reverse = _immutable
class FrozenDict(dict):
def _immutable(self, *args, **kws):
raise TypeError('cannot change object - object is immutable')
__setitem__ = _immutable
__delitem__ = _immutable
pop = _immutable
popitem = _immutable
clear = _immutable
update = _immutable
setdefault = _immutable
class safe_property(property):
def __get__(self, obj, objtype=None):
candidate = super().__get__(obj, objtype)
if isinstance(candidate, dict):
return FrozenDict(candidate)
elif isinstance(candidate, list):
return FrozenList(candidate)
elif isinstance(candidate, set):
return frozenset(candidate)
else:
return candidate
class Foo:
def __init__(self):
self._internal_lst = [1]
@property
def internal_lst(self):
return self._internal_lst
@safe_property
def internal_lst_safe(self):
return self._internal_lst
if __name__ == '__main__':
foo = Foo()
foo.internal_lst.append(2)
# foo._internal_lst is now [1, 2]
foo.internal_lst_safe.append(3)
# this throws an exception
在这个其他人的意见很感兴趣,因为我还没有看到这个实现在别处。