通过属性和文档实现只读属性的区别

3
在Python中,我正在编写一个类,其中一些属性必须在对象的生命周期内保持不变。
一种方法是使用属性(properties):
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()

这是否被视为一种良好的实践?我是否应该仅依赖文档来展示哪些属性是可写的,哪些不是?还有其他建议吗?

4个回答

1
您可以稍微修改代码,以至少得到一个投诉。
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,我认为最好还是抱怨一下。


1
我的代码在试图修改属性 Foo.x 时已经引发了 AttributeError 异常,当存在一个没有 setter 的属性时,这是默认行为。因此,我认为添加 setter 是没有用的。 - ndou
@NicolasBedou 确实,但它是否也提到了为什么会引发这种情况?这非常重要。此外,我认为你的问题不是关于保护,而是关于如果有人在函数或其他地方使用实例属性时是否会意外覆盖它,而不一定是限制属性访问。 - percusse

1

首先请注意,几乎没有什么技术手段可以阻止某人访问您对象的实现并进行修改,如果他真的想这样做。

现在针对你的问题/示例:

  • 使用一个实现属性(_x)和只读属性可以使意图非常清晰。

  • 如果您的列表确实应该是不可变的,请使用 tuple()。这在语义上是有争议的(tuple 用于基于位置的记录 - 想想关系型数据库记录等 - 而不是不可变列表),但确保它将是不可变的,并避免复制。

  • 仍然需要明确记录此属性应“在实例的生命周期内保持不变”。


如果有人真的想修改私有属性,那已经不再是我的事了。我更关心的是意外修改。元组似乎比列表更合适。不幸的是,并非总是存在可变类型的不可变等价物:例如字典、numpy.ndarray等。 - ndou

0

我相信你想要的是:

示例 #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对象时,一旦创建了任何属性,就无法更改它们。这非常方便。

namedtuples和__slots__很方便,但如果一个类包含可写和只读属性怎么办?而且,为了防止任何修改,返回可变属性的副本是推荐的做法吗? - ndou
我认为这完全取决于您的确切需求。 - James Mills

0

如果您想让属性不可修改,请不要在取消引用时返回副本:这只会进一步混淆事情并导致奇怪的错误。如果您想强制实现不可修改性,请将其转换为属性,并使设置器拒绝修改它。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接