通过属性与通过文档的只读属性

Read-only attributes via properties versus via documentation

在 Python 中,我正在编写一个 class,其中某些属性必须在对象生命周期内保持不变。

一种方法是使用属性:

class Foo:
    def __init__(self, x, y):
        '''Create a Foo object

        x -- list of int (READ-ONLY)
        y -- float
        '''
        # Save x and y and ensure they are int
        self._x = [int(xx) for xx in x]
        self.y = float(y)

    @property
    def x(self):
        # Return a copy of self._x to prevent accidental modification
        return self._x.copy()

这被认为是一种好的做法吗?我应该只依靠文档来展示哪些属性是可写的,哪些不是?还有其他建议吗?

我相信你追求的是:

示例 #1:

from collections import namedtuple

foo = namedtuple("Immutable", ["a", "b"])

示例 #2:

class Foo(object):
    __slots__ = ()

在这两种情况下,以任何方式设置属性或可变对象都是错误的。

演示:

>>> from collections import namedtuple
>>> class Foo(object):
...     __slots__ = ()
... 
>>> Bar = namedtuple("Bar", ["a", "b"])
>>> foo = Foo()
>>> bar = Bar(1, 2)
>>> foo.a = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'a'
>>> foo.a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'a'
>>> bar.a
1
>>> bar.a = 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute

请注意,对于 namedtuple 对象,您无法在创建对象后更改其任何属性。这很方便。

首先请注意,几乎没有任何技术方法可以阻止某人访问您的对象的实现并在他真的想要的情况下对其进行修改。

现在回答/你的问题/例子:

  • 使用实现属性 (_x) 和只读 属性 使意图非常明确

  • 如果您的列表真的应该是不可变的,请改用 tuple()。它在语义上值得商榷(tuple 用于基于位置的记录 - 考虑关系数据库记录等 - 不是不可变列表),但要确保它是不可变的并且避免必须制作副本。

  • 您仍然想要清楚地记录此属性应该"remain constant"实例的生命周期。

你可以稍微修改你的代码,至少得到一个投诉

class Foo:
    def __init__(self, x, y):
        '''Create a Foo object

        x -- list of int (READ-ONLY)
        y -- float
        '''
        # Save x and y and ensure they are int
        self._x = [int(xx) for xx in x]
        self.y = float(y)

    @property
    def x(self): return self._x

    @x.setter
    def x(self,value):
        raise AttributeError('Don\'t touch my private parts, please')

然后,如果你尝试改变:

>>>> a = Foo([1,2,3,4,5],0.2)

>>>> a.x
     [1, 2, 3, 4, 5]

>>>> a.x = 3
Traceback (most recent call last):

  File "<ipython-input-87-0a024de0ab56>", line 1, in <module>
    a.x = 3

  File "D:/pydump/gcd_lcm_tests.py", line 230, in x
    raise AttributeError('Don\'t touch my private parts, please')

AttributeError: Don't touch my private parts, please

您可以悄悄地 pass 而不是提出异常,但我认为最好是抱怨。

如果您希望属性不可修改,不要 return 取消引用的副本:您只会进一步混淆事情并导致奇怪错误。如果你想强制不可修改,把它变成属性,让setter拒绝修改它。

效果如何取决于您的数据结构的细节。例如,如果您的属性是一个列表,您可以拦截对列表本身的修改,但如果它具有可变元素,您仍然可以修改 它们的 属性。