将可变属性作为​​Python中的不可变对象返回

时间:2014-01-29 06:54:57

标签: python python-3.x

有没有办法返回实例属性但保护它不被修改?我会更具体:

我有一个Calendar类,它使用由datetime.date个对象索引的内部字典,以及事件ID列表作为值(实际上int s,但这是无关紧要的。)

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方法,但如果属性是可变的,则根本不能保护您免受外部修改。

显而易见的答案似乎是“好吧,你为什么不归还一份清单?”。但是:

  1. 列表可能会变得太大,这种方法会被多次调用。每次复制列表都会变得有点慢。
  2. 如果用户实际想要更改日历,则返回副本会误导h(im / er)认为他们可以,因为列表的突变没有异常。
  3. 我认为如果需要,用户应该花费精力/时间制作副本。
  4. 我想知道是否有其他方法可以达到同样的目的。
  5. 另一种可能性是制作我自己的ConstantList并返回。它解决了2和3,但不是1(因为我不得不将内部的,可变列表复制到ConstantList)4。此外,对于一个非常简单的问题,它看起来似乎太多了。

    我也知道“我们都同意成年人”,并且过度保护我的实例属性是unpythonic。但我认为我的好奇心对我有好处。

2 个答案:

答案 0 :(得分:1)

创建某种不可变列表对象的唯一方法,但你可以这样做,以便它解决你所有的问题,例如:

class PrivateList(Sequence):

    def __init__(self):
        self._list = []

    def __getitem__(self, index):
        return self._list[index]

    def __len__(self):
        return len(self._list)


class Calendar:
    def __init__(self):
        self._dict = defaultdict(PrivateList)

    def add_event(self, date, event):
        self._dict[date]._list.append(event.id)

    def get_event_ids(self, date):
        return self._dict[date]

PrivateList现在公开了一个类似元组的API,因此用户不会意外地更改某些内容,但具有_listCalendar属性,{{1}}可以访问该属性来操纵数据。请注意,前导下划线只是一种约定,并不像c ++这样的语言实际强制执行私有作用域。 Here是一个可以更好地解释范围的问题。

答案 1 :(得分:0)

<块引用>

另一种可能性是制作我自己的 ImmutableList 并返回它。它可以解决 2 和 3,但不能解决 1(因为我必须将内部可变列表复制到 ImmutableList),也不能 4。

在不可变列表(例如Calendar)的初始化期间您不必复制tuple可变列表:您可以通过存储一个ImmutableList >引用到 Calendar 可变列表(这是 Proxy pattern 的示例)。这与 @aquavitae’s solution 不同,后者将可变列表存储为 ImmutableList 的私有属性,并从 Calendar 对其进行变异,从而危及 encapsulation

import collections


class ImmutableList(collections.abc.Sequence):

    def __init__(self, list):
        self._list = list

    def __getitem__(self, index):
        return self._list[index]

    def __len__(self):
        return len(self._list)


class Calendar:

    def __init__(self):
        self._dict = collections.defaultdict(list)

    def add_event(self, date, event):
        self._dict[date].append(event.id)

    def get_event_ids(self, date):
        return ImmutableList(self._dict[date])