何时应该将属性设为私有并设置为只读?

127 投票
10 回答
159744 浏览
提问于 2025-04-17 14:08

我不太清楚什么时候属性应该是私有的,以及我是否应该使用 property

我最近看到有人说,使用设置器和获取器在 Python 中并不算优雅,但使用 property 装饰器是可以的。

但是如果我有一个属性,它不应该从类外部被设置,但可以被读取(只读属性)。这个属性应该是私有的吗?我说的私有是指用下划线开头,比如 self._x

如果是的话,那我该怎么读取它而不使用获取器呢?我现在知道的唯一方法是这样写:

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

这样我可以通过 obj.x 来读取这个属性,但我不能用 obj.x = 1 来设置它,所以这样是可以的。

但是我真的需要在意一个不应该被设置的对象吗?也许我应该就这样放着不管。但另一方面,我不能用下划线,因为读取 obj._x 对用户来说很奇怪,所以我应该用 obj.x,但这样用户又不知道他不应该设置这个属性。

你有什么看法和做法呢?

10 个回答

71

这里有一种方法可以避免假设

所有用户都是成年人,因此他们应该自己负责正确使用这些东西。

请查看我下面的更新

使用 @property 的时候,代码会显得很啰嗦,例如:

   class AClassWithManyAttributes:
        '''refactored to properties'''
        def __init__(a, b, c, d, e ...)
             self._a = a
             self._b = b
             self._c = c
             self.d = d
             self.e = e

        @property
        def a(self):
            return self._a
        @property
        def b(self):
            return self._b
        @property
        def c(self):
            return self._c
        # you get this ... it's long

使用

没有下划线:这是一个公共变量。
一个下划线:这是一个受保护的变量。
两个下划线:这是一个私有变量。

除了最后一种,其他的都是一种约定。如果你真的很努力,还是可以访问带有双下划线的变量。

那么我们该怎么办呢?难道就放弃在Python中使用只读属性吗?

看这里! read_only_properties 装饰器来帮忙了!

@read_only_properties('readonly', 'forbidden')
class MyClass(object):
    def __init__(self, a, b, c):
        self.readonly = a
        self.forbidden = b
        self.ok = c

m = MyClass(1, 2, 3)
m.ok = 4
# we can re-assign a value to m.ok
# read only access to m.readonly is OK 
print(m.ok, m.readonly) 
print("This worked...")
# this will explode, and raise AttributeError
m.forbidden = 4

你可能会问:

read_only_properties 是从哪里来的?

很高兴你问了,这里是 read_only_properties 的源代码:

def read_only_properties(*attrs):

    def class_rebuilder(cls):
        "The class decorator"

        class NewClass(cls):
            "This is the overwritten class"
            def __setattr__(self, name, value):
                if name not in attrs:
                    pass
                elif name not in self.__dict__:
                    pass
                else:
                    raise AttributeError("Can't modify {}".format(name))

                super().__setattr__(name, value)
        return NewClass
    return class_rebuilder

更新

我从没想到这个回答会引起这么多关注。出乎意料的是,它确实引起了关注。这让我有了动力去创建一个你可以使用的包。

$ pip install read-only-properties

在你的Python环境中:

In [1]: from rop import read_only_properties

In [2]: @read_only_properties('a')
   ...: class Foo:
   ...:     def __init__(self, a, b):
   ...:         self.a = a
   ...:         self.b = b
   ...:         

In [3]: f=Foo('explodes', 'ok-to-overwrite')

In [4]: f.b = 5

In [5]: f.a = 'boom'
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-5-a5226072b3b4> in <module>()
----> 1 f.a = 'boom'

/home/oznt/.virtualenvs/tracker/lib/python3.5/site-packages/rop.py in __setattr__(self, name, value)
    116                     pass
    117                 else:
--> 118                     raise AttributeError("Can't touch {}".format(name))
    119 
    120                 super().__setattr__(name, value)

AttributeError: Can't touch a
99

我想说几句,Silas Ray说得很对,不过我觉得可以加个例子。;-)

Python是一种类型不安全的语言,所以你总是得相信使用你代码的人会像个正常人一样使用它。

根据PEP 8的规定:

只有在非公开的方法和实例变量前加一个下划线。

如果你想在一个类里创建一个“只读”的属性,可以使用@property这个装饰器。使用这个装饰器时,你需要从object继承,以便使用新式类。

例子:

>>> class A(object):
...     def __init__(self, a):
...         self._a = a
...
...     @property
...     def a(self):
...         return self._a
... 
>>> a = A('test')
>>> a.a
'test'
>>> a.a = 'pleh'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
88

一般来说,写Python程序的时候,我们应该假设所有用户都是成年人,能够自己负责正确使用这些程序。不过,有一些特殊情况,比如某些属性的值是计算出来的,或者是从某个固定的数据源读取的,这种情况下就不应该让用户随便修改这些属性。对于这种情况,通常我们会使用只读属性,也就是只提供获取值的功能,不让用户去设置它。

撰写回答