【问题标题】:Read-only attributes via properties versus via documentation通过属性与通过文档的只读属性
【发布时间】:2015-08-17 14:51:23
【问题描述】:

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

一种方法是使用属性:

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

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

【问题讨论】:

    标签: python properties attributes private readonly


    【解决方案1】:

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

    现在写/您的问题/示例:

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

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

    • 您仍然确实希望清楚地记录此属性应在实例的生命周期内“保持不变”。

    【讨论】:

    • 如果有人真的想修改私有属性,那不再是我的事了。我更担心意外修改。元组似乎确实比列表更合适。不幸的是,可变类型并不总是不可变的等价物:例如字典、numpy.ndarray 等。
    【解决方案2】:

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

    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 而不是引发异常,但在我看来,最好是抱怨。

    【讨论】:

    • 我的代码在尝试修改属性Foo.x 时已经引发了一个AttributeError 异常,这是当有一个没有setter 的属性时的默认行为。因此,我觉得添加 setter 没有用处。
    • @NicolasBedou 是的,但它是否也提到了为什么会提高?这很重要。另外我认为你的问题不是关于保护,而是如果有人在函数内部或其他地方使用实例属性是否被意外覆盖,不一定限制属性访问。
    【解决方案3】:

    我相信你所追求的被称为:

    示例 #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 很方便,但是如果一个类同时包含可写和只读属性怎么办?是否推荐返回可变属性的副本以防止任何修改?
    • 我认为这一切都取决于您的确切要求。
    【解决方案4】:

    如果您希望属性不可修改,不要在取消引用时返回副本:您只会进一步混淆事物并导致奇怪的错误。如果您想强制执行不可修改性,请将其设为属性并让 setter 拒绝修改它。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-02-07
      • 2018-04-19
      • 1970-01-01
      • 2015-03-15
      • 2021-08-02
      • 2013-09-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多