Python的使用属性装饰只读列表

问题描述 投票:10回答:4

简洁版本

我可以使用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)

感谢大家。

python properties getter shallow-copy
4个回答
6
投票

你可以有方法返回在你的原始列表的包装 - collections.Sequence可能是帮助写它。或者,你可以返回一个tuple - 复制列表到一个元组通常是可以忽略的开销。

虽然最终,如果用户想改变基础列表,他们可以而且也确实没有什么可以做,以阻止他们。 (毕竟,他们可以直接访问self._myList如果他们想的话)。

我认为Python的方式做这样的事情是记录他们不应该改变的列表,如果这样做,那么这是他们的错,当他们的程序崩溃和烧伤。


2
投票

尽管我做了一个属性

不要紧,如果它是一个属性,你是返回一个指针列表,以便您可以修改它。

我建议建立一个list subclass并覆盖append__add__方法


0
投票

为什么是最好元组的列表?元组是,对于大多数的意图和目的,“不可改变的名单” - 这样自然就会为只读,不能直接设置或修改的对象采取行动。在这一点上,人们只需不写说二传手该属性。

>>> 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
>>> 

0
投票

返回一个元组或子类列表返回的提出的解决方案,似乎是不错的解决方案,但我不知道是否会再容易不过了子类的装饰呢?不知道它,这可能是一个愚蠢的想法:

  • 使用这种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

在这个其他人的意见很感兴趣,因为我还没有看到这个实现在别处。

© www.soinside.com 2019 - 2024. All rights reserved.