【问题描述】:

我对 Python 比较陌生,所以我希望我没有遗漏一些东西,但是这里......

我正在尝试编写一个 Python 模块,并且我想创建一个具有“私有”属性的类,该属性只能(或者可能“应该”)通过模块中的一个或多个函数进行修改。这是为了使模块更加健壮,因为在这些函数之外设置此属性可能会导致不需要的行为。例如,我可能有:

  1. 为散点图存储 x 和 y 值的类,Data
  2. 从文件中读取 x 和 y 值并将它们存储在类中的函数 read()
  3. 绘制它们的函数,plot()

在这种情况下,我希望用户无法执行以下操作:

data = Data()
read("file.csv", data)
data.x = [0, 3, 2, 6, 1]
plot(data)

我意识到在名称中添加一个前导下划线向用户表明不应更改该属性,即重命名为 _x 并添加一个属性装饰器,以便用户可以访问该值不感到内疚。但是,如果我也想添加一个 setter 属性怎么办:

class Data(object):
    _x = []
    _y = []
    @property
    def x(self):
        return self._x
    @x.setter
    def x(self, value):
        # Do something with value
        self._x = value

我现在处于与以前相同的位置 - 用户不能再直接访问属性 _x,但他们仍然可以使用以下方法设置它:

data.x = [0, 3, 2, 6, 1]

理想情况下,我会将属性函数定义重命名为 _x(),但这会导致混淆 self._x 的实际含义(取决于它们被声明,这似乎导致 setter 被递归调用或 setter 被忽略以支持属性)。

我能想到的几个解决方案:

  1. 在属性__x 中添加一个双前导下划线,以便名称变得混乱,并且不会与setter 函数混淆。据我了解,这应该保留给一个类不希望与可能的子类共享的属性,所以我不确定这是否合法使用。
  2. 重命名属性,例如_x_stored。虽然这完全解决了问题,但它使代码更难阅读并引入了命名约定问题——我要重命名哪些属性?只是那些相关的?只有那些有属性的?只有这个班级的人?

以上任一解决方案都适用吗?如果没有,有没有更好的方法来解决这个问题?

编辑

感谢您迄今为止的回复。 cmets抛出的几点:

  1. 我想保留 setter 属性给我的额外逻辑 - 上面示例中的 #Do something with value 部分 - 所以通过直接访问 self 在内部设置属性。 _x 没有解决问题。
  2. 删除 setter 属性并创建一个单独的函数 _set_x() 确实解决了问题,但不是一个非常简洁的解决方案,因为它允许在两个不同的设置中设置 _x方式 - 通过调用该函数或通过 self._x 的直接访问。然后,我必须跟踪哪些属性应该由它们自己的(非属性)setter 函数设置,哪些应该通过直接访问进行修改。我可能更愿意使用我上面建议的解决方案之一,因为即使它们在类中弄乱了命名约定,但它们在类外的使用至少是一致的,即它们都使用属性的语法糖.如果没有办法以更简洁的方式做到这一点,那么我想我只需要选择造成干扰最小的那个。

【问题讨论】:

  • 我很困惑为什么要定义一个不希望人们使用的 setter。
  • @cmd - 我仍然希望能够在模块内的其他功能中使用设置器。例如,我可能有一个需要能够设置 x 和 y 值的 interpolate() 函数。
  • 但是那些可以只设置 _x_y
  • 如果您想在设置时捕获额外的逻辑,我会创建一个内部使用的函数 _setx()。我认为其他选项的复杂性不值得有限的语法糖。

标签: python properties attributes private


【解答1】:

如果您想阻止用户更改属性,但希望明确他们可以读取它,我会使用 @property 而不提供 setter,类似于您之前描述的内容:

class Data(object):
    def __init__(self):
       self._x = []
       self._y = []

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

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

我知道您提到“如果我想向属性添加 setter 怎么办?”,但我想我会反驳:如果您不希望您的客户能够设置属性,为什么要添加 setter ?在内部,您可以直接访问 self._x

对于直接访问 _x_y 的客户端,任何带有 '_' 前缀的变量在 Python 中都被理解为“私有”,因此您应该相信您的客户要遵守。如果他们不遵守这一点,最终把事情搞砸了,那是他们自己的错。这种心态与许多其他语言(C++、Java 等)相反,在这些语言中,保持数据的私密性非常重要,但 Python 的文化在这方面只是不同。

编辑

另一个注意事项,由于在这种特殊情况下您的私有属性是列表,它们是可变的(与字符串或整数不同,它们是不可变的),客户端最终可能会意外地更改它们:

>>> d = Data()
>>> print d.x
['1', '2']
>>> l = d.x
>>> print l
['1', '2']
>>> l.append("3")
>>> print d.x
['1', '2', '3']  # Oops!

如果你想避免这种情况,你需要你的财产返回一份清单:

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

【问题讨论】:

  • 我知道使用 self._x 的直接访问在内部设置值很容易,但这消除了拥有 setter 给我的优势,即 # 做一些有价值的事情 bit.
  • 啊,你的意思是让 setter 做的不仅仅是简单的 self._x = blah?我想在这种情况下,您要么也必须将 @property 添加到私有属性(我认为您提到过您不喜欢),或者在您想分配给私有时始终记得调用一些内部辅助函数财产。
  • 有没有更优雅的方法来做到这一点,不会引入不一致的命名约定?请参阅原始帖子编辑。 (@cmd, @kindall)