Python中的"public"或"private"属性?什么是最好的方法?

San*_*nda 49 python oop encapsulation properties

在Python中,我有以下示例类:

class Foo:
    self._attr = 0

    @property
    def attr(self):
        return self._attr

    @attr.setter
    def attr(self, value):
        self._attr = value

    @attr.deleter
    def attr(self):
        del self._attr
Run Code Online (Sandbox Code Playgroud)

如您所见,我有一个简单的"私有"属性"_attr"和一个访问它的属性.有很多代码来声明一个简单的私有属性,我认为它不尊重"KISS"哲学来声明所有属性.

那么,如果我不需要特定的getter/setter/deleter,为什么不将所有属性声明为公共属性?

我的答案是:因为封装原则(OOP)说不然!

什么是最好的方法 ?

Bri*_*per 83

通常,Python代码努力遵循统一访问原则.具体而言,接受的方法是:

  • 直接公开实例变量,例如foo.x = 0,不允许foo.set_x(0)
  • 如果需要在方法中包含访问,无论出于何种原因使用@property,这将保留访问语义.也就是说,foo.x = 0现在调用foo.set_x(0).

这种方法的主要优点是调用者可以这样做:

foo.x += 1
Run Code Online (Sandbox Code Playgroud)

即使代码可能真的在做:

foo.set_x(foo.get_x() + 1)
Run Code Online (Sandbox Code Playgroud)

第一个语句无限可读.但是,对于属性,您可以添加(在开头或稍后)使用第二种方法获得的访问控制.

另请注意,以单个下划线开头的实例变量通常是私有的.也就是说,下划线向其他开发人员发出信号,表示您认为该值是私有的,并且他们不应该直接混淆它; 但是,语言中的任何内容都不能阻止他们直接搞乱它.

如果你使用双前导下划线(例如__x),Python会对名称进行一些模糊处理.但是,仍然可以通过其混淆的名称从类外部访问该变量.这不是真正私密的.它只是......更不透明.并且有反对使用双下划线的有效论据; 首先,它可以使调试更加困难.

  • 我总是指出,与Ruby,Python,Scala和C#(仅举几例)不同,Java实际上更糟糕,因为它明显无法遵循统一访问原则.因此,编码器必须在防御中键入(或IDE必须生成)getter和setter,其中大多数都不会添加任何实际值 - 所有这些都意味着在维护类时需要更多无用的代码. (4认同)
  • 我如何与Java开发人员争辩/讨论这不会使编程语言“变差”,或者不仅仅是“缺少功能”? (2认同)
  • @BrianClapper“未能遵守统一访问原则”——来自 JVM 世界,这是我们认为要避免的,因为它使代码不太“安全”。我的意思是,让我们假设我们在类内部有一些可变属性,并且您想向其他人隐藏它的业务逻辑......不确定我是否希望公开。我有兴趣了解为什么您认为在构建语言时遵守这一原则很重要。完全开放,给我一些参考 (2认同)
  • @milosmns 如果有人有权访问代码库,您就无法向他们隐藏业务逻辑。请参阅[此](https://en.wikipedia.org/wiki/Uniform_access_principle#Explanation) 和[此](https://martinfowler.com/bliki/UniformAccessPrinciple.html) 了解为什么 UAP 可能更好,除了答案本身。 (2认同)

bra*_*ese 16

"dunder"(双下划线__)前缀可防止访问属性,但通过访问器除外.

class Foo():
    def __init__(self):
        self.__attr = 0

    @property
    def attr(self):  
        return self.__attr

    @attr.setter
    def attr(self, value):
        self.__attr = value

    @attr.deleter
    def attr(self):
        del self.__attr
Run Code Online (Sandbox Code Playgroud)

一些例子:

>>> f = Foo()
>>> f.__attr                          # Not directly accessible.
Traceback (most recent call last):
  File "<input>", line 1, in <module>
AttributeError: 'Foo' object has no attribute '__attr'
>>> '__attr' in f.__dir__()           # Not listed by __dir__()
False
>>> f.__getattribute__('__attr')      # Not listed by __getattribute__()
Traceback (most recent call last):
  File "<input>", line 1, in <module>
AttributeError: 'Foo' object has no attribute '__attr'
>>> f.attr                            # Accessible by implemented getter.
0
>>> f.attr = 'Presto'                 # Can be set by implemented setter.
>>> f.attr
'Presto'
>>> f.__attr = 'Tricky?'              # Can we set it explicitly?
>>> f.attr                            # No. By doing that we have created a 
'Presto'                              # new but unrelated attribute, same name.
Run Code Online (Sandbox Code Playgroud)

  • 但是... [您*可以*通过特殊方式访问该属性](https://docs.python.org/2/tutorial/classes.html#private-variables-and-class-local-references):` f.__getattribute__('_Foo__attr')`。 (3认同)

Len*_*bro 10

很简单,OOP原则是错误的.为什么这是一个长期的讨论,导致火焰战争,可能是该网站的主题.:-)

在Python中没有私有属性,你无法保护它们,这绝不是一个真正的问题.所以不要.简单!:)

接下来的问题是:你是否应该有一个领先的下划线.在你在这里的例子中你绝对不应该.Python中的一个主要下划线是一种约定,表明某些内容是内部的,而不是API的一部分,并且您应该自担风险使用它.这显然不是这里的情况,但它是一种常见且有用的惯例.

  • “_Python 中没有私有属性_”。很多次我不得不解释封装的工作原理与其他编程语言(如 Java)不同...... (3认同)

Tyl*_*ves 6

Python没有公共OR私有属性.所有代码都可以访问所有属性.

self.attr = 0 #Done
Run Code Online (Sandbox Code Playgroud)

你的方法并没有以任何方式使_attr私有,这只是一些混淆.

  • 然而,使用一个前导下划线(或两个,取决于)是一个完善的Python约定,大致意思是"嘿,不要直接访问它.我可能会改变它,删除它,或者在将来搞乱它本课程的修订." 当然,这不是强制隐私; 然而,它*是*,在Python中广泛使用,它被理解为*传统*隐私的一种形式. (5认同)
  • 我知道,但这不是我的问题.正如您在我的帖子中所看到的,"私人"和"公共"都是用引号括起来的. (2认同)

W.P*_*rin 5

请参阅此链接:https : //docs.python.org/2/tutorial/classes.html

9.6. 私有变量和类本地引用

Python 中不存在只能从对象内部访问的“私有”实例变量。但是,大多数 Python 代码都遵循一个约定:带有下划线前缀的名称(例如 _spam)应该被视为 API 的非公开部分(无论是函数、方法还是数据成员) . 它应被视为实施细节,如有更改,恕不另行通知。

由于类私有成员有一个有效的用例(即避免名称与子类定义的名称的名称冲突),因此对这种称为名称修改的机制的支持有限。任何 __spam 形式的标识符(至少两个前导下划线,最多一个尾随下划线)在文本上替换为 _classname__spam,其中 classname 是当前类名,去掉了前导下划线。只要它出现在类的定义中,就可以不考虑标识符的句法位置来完成这种修改。