【发布时间】:2014-02-20 20:17:30
【问题描述】:
有没有办法返回一个可变属性但保护它不被修改?我会更具体:
我有一个 Calendar 类,它使用内部字典映射 datetime.date 对象到 int 事件 ID 的 list 对象:
class Calendar:
def __init__(self):
self._dict = {}
def add_event(self, date, event):
if date in self._dict:
self._dict[date].append(event.id)
else:
self._dict[date] = [event.id]
def get_event_ids(self, date):
if date in self._dict:
return self._dict[date]
else:
return []
如您所见,它非常简单。我只想封装列表创建和事件 ID 提取逻辑。但我想保护这个日历不被无意修改。更具体地说,不知情的用户实际上可以这样做:
calendar = Calendar()
calendar.add_event(some_date, some_event)
event_ids = calendar.get_event_ids(some_date)
event_ids[0] = 42 # modifies the internal list in the calendar
我知道这个例子看起来有点特别,但如果你仔细想想,用户可能想以某种方式修改日历返回的列表,为什么他希望这样做会修改里面的日历?
另一件(类似的)事情是当我想设置一个只读属性时。 Pythonic 方法是使用 property 而不使用 setter 方法,但如果属性是可变的,则根本无法保护您免受外部修改。
显而易见的答案似乎是“好吧,你为什么不返回列表的副本?”但是:
- 列表可能会变得太大,而且这个方法会被调用很多次。每次复制列表可能会有点慢。
- 如果用户确实想要更改日历,返回副本会误导 h(im/er) 认为他们可以,因为列表的变化不会引发异常。
- 我认为,如果需要,用户应该花时间/精力来制作副本。
- 我想知道是否有其他方法可以实现相同的目标。
另一种可能性是制作我自己的ImmutableList 并返回它。它解决了 2 和 3,但不是 1(因为我必须将内部可变列表复制到 ImmutableList)也不是 4。此外,对于一个非常简单的问题来说,这似乎工作量太大。
我也知道“我们都是成年人”,过度保护我的实例属性是不合常规的。但我认为我的好奇心战胜了我。
【问题讨论】:
-
你可以返回迭代器而不是列表本身。
-
如果你想要一个不可变的集合,那么使用一个不可变的连接(例如
tuple)。只需将get_event_ids修改为return tuple(self._dict[date])。顺便说一句:你真的应该在你的情况下使用defaultdict(list)并避免,这可以让你避免所有ifs。 -
你的英语绝对高于平均水平,恕我直言。 :)
-
@Bakuriu 但是从列表中创建一个新元组并不比复制它花费相同(或更多)的时间?无论如何,感谢您的
defaultdict提示! -
@Laski 这就是
tuple所做的。构建一个列表需要相同的时间,但它是不可变的。避免复制的唯一方法是使用视图(就像您接受的答案一样,尽管我相信它可以改进。例如最好使用defaultdict(list),修改PrivateList,使其接受@987654341 @ in__init__并将get_event_ids更改为返回PrivateList(self._dict[date])。这样可以减少创建大量视图和访问私有属性的开销。
标签: python collections immutability