我应该在 Python 中使用名称错位吗?

在其他语言中,有助于产生更好代码的一般准则总是尽可能隐藏所有内容。如果对变量应该是私有的还是保护的有疑问,最好使用私有的。

Python 也是如此吗?我是否应该一开始就在所有内容上使用两个前导下划线,并且只在需要时使它们不那么隐藏(只有一个下划线) ?

如果约定只使用一个下划线,我还想知道其基本原理。

这是我在 JBernardo 的回答上留下的评论。它解释了我为什么问这个问题,以及为什么我想知道为什么 Python 不同于其他语言:

我的语言训练你认为一切都应该只是公开的需要,没有更多。其原因是,这将减少依赖性,并使代码更安全地进行更改。Python 逆向操作的方式——从 public 开始,然后到 hide ——对我来说很奇怪。

58998 次浏览

当有疑问的时候,让它“公开”——我的意思是,不要添加任何东西来模糊属性的名称。如果您有一个具有某种内部价值的类,那么不要为它操心。而不是写:

class Stack(object):


def __init__(self):
self.__storage = [] # Too uptight


def push(self, value):
self.__storage.append(value)

默认情况下这样写:

class Stack(object):


def __init__(self):
self.storage = [] # No mangling


def push(self, value):
self.storage.append(value)

这肯定是一种有争议的做事方式。Python 新手讨厌它,甚至一些 Python 老手也鄙视这个默认设置——但它是默认设置,所以我建议你遵循它,即使你感到不舒服。

如果你 真的想发送消息“不能碰这个!”对于用户来说,通常的方法是在变量前面加上 下划线。这只是一种惯例,但人们理解它,并在处理这类事情时加倍小心:

class Stack(object):


def __init__(self):
self._storage = [] # This is ok, but Pythonistas use it to be relaxed about it


def push(self, value):
self._storage.append(value)

这对于避免属性名和属性名之间的冲突也很有用:

 class Person(object):
def __init__(self, name, age):
self.name = name
self._age = age if age >= 0 else 0
     

@property
def age(self):
return self._age
     

@age.setter
def age(self, age):
if age >= 0:
self._age = age
else:
self._age  = 0

那双下划线呢?我们主要使用 以避免方法的意外重载和名称与超类的属性冲突的双下划线魔术。如果您编写一个要多次扩展的类,那么它可能非常有价值。

如果您希望将其用于其他目的,可以使用它,但这既不是通常的做法,也不是推荐的做法。

为什么会这样?好吧,通常的 Python 风格并不强调将事情私有化——恰恰相反!这有很多原因-其中大多数是有争议的... 让我们看看其中的一些。

巨蟒有特性

今天,大多数面向对象语言使用相反的方法: 不应该使用的东西不应该是可见的,因此属性应该是私有的。从理论上讲,这将产生更易管理、耦合程度更低的类,因为没有人会不计后果地更改对象的值。

然而,事情没那么简单。例如,Java 类有许多 getter,它们只有 走开的值和 还有的值,它们只有 准备好了的值。比方说,您需要七行代码来声明一个属性—— Python 程序员会说这是不必要的复杂。另外,您编写了大量代码来获得一个公共字段,因为您可以在实践中使用 getter 和 setter 更改它的值。

那么,为什么要遵循这种默认的私有政策呢?默认情况下,只需将属性公开即可。当然,这在 Java 中是有问题的,因为如果您决定在属性中添加一些验证,它将要求您更改所有:

person.age = age;

在你的代码中,比如说,

person.setAge(age);

setAge()是:

public void setAge(int age) {
if (age >= 0) {
this.age = age;
} else {
this.age = 0;
}
}

所以在 Java (和其他语言)中,默认使用 getter 和 setter,因为它们写起来很烦人,但是如果你发现自己处于我所描述的情况下,它们可以为你节省很多时间。

但是,由于 Python 有属性,因此不需要在 Python 中执行此操作:

 class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age

... 然后你决定验证年龄,你不需要改变你的代码的 person.age = age部分。只需添加一个属性(如下所示)

 class Person(object):
def __init__(self, name, age):
self.name = name
self._age = age if age >= 0 else 0
     

@property
def age(self):
return self._age
     

@age.setter
def age(self, age):
if age >= 0:
self._age = age
else:
self._age  = 0

假设您可以这样做,并且仍然使用 person.age = age,为什么要添加私有字段、 getter 和 setter?

(请参阅 Python 不是 Java本文介绍了使用 getter 和 setter 的危害。)。

无论如何,一切都是可见的——试图隐藏会使你的工作复杂化

即使在具有私有属性的语言中,您也可以通过一些反射/自省库来访问它们。而且人们经常这样做,在框架中,为了解决紧急需求。问题是,自省库只是一种复杂的方法,可以用来处理公共属性。

由于 Python 是一种非常动态的语言,因此将这种负担添加到类中会适得其反。

问题是不可能看到-这是 需要看到

对于 Pythonista 来说,封装不是不能看到类的内部,而是可以避免看到它。封装是组件的属性,用户可以使用该组件而无需关心内部细节。如果您可以使用一个组件而不必担心它的实现,那么它就是封装的(在 Python 程序员看来)。

现在,如果您编写了一个类,您可以使用它而不需要考虑实现细节,那么出于某种原因,如果您使用 想要来查看该类内部,就不会有任何问题。重点是: 你的 API 应该是好的,其余的都是细节。

圭多说的

嗯,这是没有争议的: 他是这么说的。(寻找“开放式和服。”)

这就是文化

是的,有一些原因,但没有关键的原因。这主要是 Python 编程的一个文化方面。坦率地说,这也可能是另一种方式,但事实并非如此。另外,您也可以很容易地反过来问: 为什么有些语言默认使用私有属性?出于与 Python 实践相同的主要原因: 因为它是这些语言的文化,而且每种选择都有优缺点。

既然已经存在这种文化,那么最好还是遵循它。否则,当您在 Stack Overflow: 中提出问题时,Python 程序员会告诉您从代码中删除 __,这会使您感到恼火。)

乍一看,它应该与其他语言相同(在“其他”下,我指的是 Java 或 C + +) ,但它不是。

在 Java 中,您将所有不能在外部访问的变量设置为 private。同时,在 Python 中你不能做到这一点,因为没有“私有性”(正如 Python 原则之一所说的——“我们都是成年人”)。所以双下划线只表示“伙计们,不要直接使用这个字段”。同样的意思有单下划线,当您必须从所考虑的类继承时,它不会引起任何麻烦(只是双下划线可能引起的问题的一个例子)。

因此,我建议您在默认情况下对“私有”成员使用单下划线。

我不认为实践会产生更好的代码。可见性修饰符只会分散您对手头任务的注意力,作为副作用,会强制您按照预期使用界面。一般来说,强制可见性可以防止程序员在没有正确阅读文档的情况下把事情搞砸。

一个更好的解决方案是 Python 所鼓励的路线: 您的类和变量应该有良好的文档,并且它们的行为清晰。源应该是可用的。这是一种更加可扩展和可靠的编写代码的方式。

我在 Python 中的策略是:

  1. 只是写该死的东西,不要假设你的数据应该如何保护。这假设您编写代码是为了为您的问题创建理想的接口。
  2. 对于 可能吧不会在外部使用,也不是正常“客户端代码”接口的一部分的内容,使用前导下划线。
  3. 使用双下划线只是为了方便内部的类,或将造成相当大的损害,如果意外暴露。

最重要的是,一切都应该清楚。如果有其他人将使用它,则将其记录下来。如果您希望它在一年的时间内有用,请将它记录下来。

顺便说一句,您实际上应该使用其他语言中的 受到保护: 您永远不会知道您的类可能在以后继承,以及它可能被用于什么目的。最好只保护那些您确定不能或不应该由外部代码使用的变量。

第一: 你为什么要隐藏你的数据? 为什么隐藏数据如此重要?

大多数时候你并不真的想这么做,但是你这么做了,因为其他人也在这么做。

如果您真的真的不想让人们使用某些东西,在它前面添加 下划线。就是这样... Python 工作者知道,只有一个下划线的东西并不能保证每次都能正常工作,而且可能会在你不知情的情况下发生变化。

这就是我们的生活方式,我们可以接受。

使用两个下划线会使你的类变得非常糟糕,以至于你都不想用这种方式工作。

您不应该从私有数据开始,并在必要时将其公开。相反,您应该从确定对象的接口开始。也就是说,你应该从弄清楚世界看到了什么(公共事物)开始,然后弄清楚什么样的私人事物是实现这一目标所必需的。

其他语言使得曾经公开的东西很难私下化。也就是说,如果我将变量设置为 private 或 protected,就会破坏大量代码。但是对于 python 中的属性,情况并非如此。相反,我可以通过重新排列内部数据来维护相同的接口。

_ 和 _ 之间的区别在于 python 实际上尝试强制执行后者。当然,它并没有真的很努力,但它确实让它变得很困难。仅仅告诉其他程序员其意图是什么,他们就可以自由地忽略,这样会有危险。但是忽略这个规则有时是有帮助的。这些示例包括调试、临时修改和使用第三方代码,而这些代码并不打算以您使用它的方式使用。

这个问题已经有很多好的答案了,但是我还要提供另外一个。这在一定程度上也是对那些一直声称双下划线不是私有的人的回应(它确实是私有的)。

如果你看看 Java/C # ,它们都有 private/protected/public。这些都是 编译时构造。它们仅在编译时强制执行。如果在 Java/C # 中使用反射,就可以很容易地访问 private 方法。

现在,每次在 Python 中调用函数时,都会自然而然地使用反射。这些代码在 Python 中是相同的。

lst = []
lst.append(1)
getattr(lst, 'append')(1)

“ dot”语法只是后一段代码的语法糖。主要是因为使用 getattr 只有一个函数调用已经很难看了。情况只会越来越糟。

因此,不行是一个 Java/C # 版本的 private,因为 Python 不编译代码。Java 和 C # 无法检查一个函数在运行时是私有的还是公有的,因为这些信息已经消失了(而且它不知道从哪里调用这个函数)。

现在有了这些信息,双下划线的名称错位对于实现“私有性”最有意义。现在,当从“ self”实例调用一个函数时,它会注意到它以“ _ _”开头,它只是在那里执行名称修改。这只是更多的句法糖。这种语法“糖”允许在一种只使用反射进行数据成员访问的语言中使用“ private”等价物。

免责声明: 我从未听到任何 Python 开发人员说过类似的话。缺少“ private”的真正原因是文化差异,但是您也会注意到,大多数脚本/解释语言都没有 private。除了编译时之外,严格可执行的私有对任何事情都不实用。

所选择的答案很好地解释了属性是如何消除对 私人属性的需求的,但是我还要补充说明模块级别的函数是如何消除对 私人方法的需求的。

如果在模块级别将方法转换为函数,则消除了子类重写它的机会。将一些功能移动到模块级别比试图通过名称混淆来隐藏方法更加 Python 化。

首先-什么是名字损坏?

在类定义中并使用 __any_name__any_name_,即 (或更多)前导下划线和最多一个后导下划线时,将调用名称错位。

class Demo:
__any_name = "__any_name"
__any_other_name_ = "__any_other_name_"

现在:

>>> [n for n in dir(Demo) if 'any' in n]
['_Demo__any_name', '_Demo__any_other_name_']
>>> Demo._Demo__any_name
'__any_name'
>>> Demo._Demo__any_other_name_
'__any_other_name_'

不确定的时候,做什么?

表面上的用途是防止子类使用类使用的属性。

一个潜在的价值在于避免与希望覆盖行为的子类发生名称冲突,以便父类功能能够按照预期的方式工作。然而,Python 文档中的 例子并不是 Liskov 可替代的,而且我没有发现任何例子可以证明这一点。

缺点是它增加了阅读和理解代码库的认知负荷,尤其是在调试时,在源代码中看到双下划线名称,在调试器中看到错误的名称。

我个人的做法是有意避免。我的代码库非常庞大。它的罕见用途突出像一个酸痛的拇指,似乎没有正当理由。

你确实需要意识到这一点,这样当你看到它的时候你就知道了。

PEP 8

Python 标准库风格指南 PEP 8 目前表示(删节) :

关于 __names的使用存在一些争议。

如果您的类打算被子类化,并且您有不希望子类使用的属性,那么可以考虑使用双前导下划线和无后导下划线来命名它们。

  1. 请注意,只有简单的类名称被使用在错误的名称中,因此如果一个子类同时选择相同的类名称和属性名称, 您仍然可以得到名称冲突

  2. 名称错位可以使某些用途,如调试和 __getattr__(),不那么方便。然而,名称错位算法是很好的文档,很容易手动执行。

  3. 不是每个人都喜欢名字被毁掉。尽量在避免意外名称冲突和高级呼叫者可能使用名称之间取得平衡。

它是怎么工作的?

如果你在一个类定义中预设了两个下划线(没有结束双下划线) ,那么这个名字将会被破坏,并且一个下划线后跟一个类名将会被预设在对象上:

>>> class Foo(object):
...     __foobar = None
...     _foobaz = None
...     __fooquux__ = None
...
>>> [name for name in dir(Foo) if 'foo' in name]
['_Foo__foobar', '__fooquux__', '_foobaz']

请注意,只有在解析类定义时,名称才会被混淆:

>>> Foo.__test = None
>>> Foo.__test
>>> Foo._Foo__test
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'Foo' has no attribute '_Foo__test'

而且,当他们无法手动访问在类定义中定义的名称时,那些刚接触 Python 的人有时很难理解发生了什么。这并不是一个强有力的反对理由,但是如果你有一个学习型的听众,这是值得考虑的。

一个下划线?

如果约定只使用一个下划线,我还想知道其基本原理。

当我的意图是让用户远离一个属性时,我倾向于只使用一个下划线,但这是因为在我的心智模型中,子类可以访问名称(他们一直都有,因为他们可以很容易地发现错误的名称)。

如果我回顾使用 __前缀的代码,我会问他们为什么要调用名称错位,如果他们不能使用一个下划线,记住,如果子类为 class 和 class 属性选择相同的名称,尽管如此,还是会有名称冲突。

下面的代码片段将解释所有不同的情况:

  • 两个前导下划线(_ _ a)
  • 单前导下划线(_ a)
  • 无下划线(a)

    class Test:
    
    
    def __init__(self):
    self.__a = 'test1'
    self._a = 'test2'
    self.a = 'test3'
    
    
    def change_value(self,value):
    self.__a = value
    return self.__a
    

printing all valid attributes of Test Object

testObj1 = Test()
valid_attributes = dir(testObj1)
print valid_attributes


['_Test__a', '__doc__', '__init__', '__module__', '_a', 'a',
'change_value']

在这里,您可以看到 _ _ a 的名称已经更改为 _ Test _ _ a,以防止此变量被任何子类重写。这个概念在 python 中被称为“名称处理”。 你可以这样访问:

testObj2 = Test()
print testObj2._Test__a


test1

类似地,在 _ a 的情况下,变量只是通知开发人员应该将其用作该类的内部变量,即使您访问了它,python 解释器也不会做任何事情,但这不是一个好的实践。

testObj3 = Test()
print testObj3._a


test2

一个变量可以从任何地方访问它就像一个公共类变量。

testObj4 = Test()
print testObj4.a


test3

希望这个答案对你有所帮助:)

“如果对变量应该是私有的还是保护的有疑问,最好使用私有的。”是的,巨蟒也是如此。

这里有些答案是关于“约定”的,但是不要给出这些约定的链接。权威的 Python 指南 PEP 8明确指出:

如果有疑问,那么选择非公共属性; 将其设置为公共属性比将公共属性设置为非公共属性更容易。

Public 和 private 之间的区别,以及 Python 中的 名字毁坏之间的区别已经在其他答案中得到了考虑,

在这里我们不使用术语“ private”,因为在 Python 中没有属性是真正私有的(没有通常不必要的工作量)。

# Python 名称错误处理示例程序

class Demo:
__any_name = "__any_name"
__any_other_name_ = "__any_other_name_"




[n for n in dir(Demo) if 'any' in n]   # GIVES OUTPUT AS ['_Demo__any_name',
#    '_Demo__any_other_name_']