Python名称修饰


109

在其他语言中,有助于产生更好代码的通用准则总是使所有内容都尽可能隐藏。如果不确定变量是私有变量还是受保护变量,最好使用私有变量。

同样适用于Python吗?我是否应该首先在所有内容上使用两个前导下划线,并且仅在需要时才使它们的隐藏性降低(仅一个下划线)?

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

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

我来自可以训练您的语言,使您认为一切都应该仅在需要时公开,而不能再公开了。原因是这将减少依赖关系并使代码更安全地更改。Python反向做事的方式-从公开开始到隐蔽-对我来说很奇怪。

Answers:


182

如有疑问,请将其保留为“公开”-我的意思是,请勿添加任何内容以掩盖您的属性名称。如果您的类具有一些内部值,请不要理会。而不是写:

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风格并不强调将事情变成私有的-相反!造成这种情况的原因很多-大多数都引起争议...让我们看看其中的一些原因。

Python具有属性

如今,大多数OO语言都采用相反的方法:不应该使用的内容不应该可见,因此属性应该是私有的。从理论上讲,这将产生更易于管理,耦合更少的类,因为没有人会不顾一切地更改对象内部的值。

但是,它并不是那么简单。例如,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程序员会告诉您将其从代码中删除,这会使您感到烦恼:)


1.封装用于保护类不变式。不要隐藏外界不必要的细节,因为这很烦人。2.“重点是:您的API应该是好的,其余的是细节。” 这是真的。公共属性是您的API的一部分。另外,有时公共设置器是适当的(关于类的不变式),有时则不合适。具有不应该公开的公共设置器的API(违反不变式的风险)是错误的API。这意味着无论如何,您都必须考虑每个设置器的可见性,而拥有“默认值”的意义就更少了。
木星

21

首先-什么是名称修改?

当您在类定义中并使用__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

PEP 8(Python标准库样式指南)目前说(删节):

关于使用的方法存在一些争议 __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属性还是会发生名称冲突。


15

我不会说实践会产生更好的代码。可见性修改器只会分散您的注意力,而副作用是会强制您按预期使用界面。一般来说,增强可见性可以防止程序员在没有正确阅读文档的情况下搞乱事情。

一个更好的解决方案是Python鼓励的方法:应该很好地记录您的类和变量,并明确其行为。源应该可用。这是编写代码的更可扩展和可靠的方式。

我在Python中的策略是:

  1. 只需写下该死的东西,就无需假设如何保护您的数据。假定您编写用于创建问题的理想接口。
  2. 使用前导下划线表示可能不会在外部使用且不属于常规“客户端代码”界面的。
  3. 仅在课堂上纯粹方便的事情上使用双下划线,否则,如果不慎将其暴露在外会造成相当大的损害。

最重要的是,应该清楚一切都在做什么。如果其他人会使用它,请记录下来。如果希望一年之内有用,请记录下来。

附带说明,您实际上应该使用其他语言的保护:您永远都不知道您的类以后可能会继承以及它的用途。最好只保护您确定不能或不应被外来代码使用的那些变量。


9

您不应该从私有数据开始,而是在必要时将其公开。相反,您应该首先确定对象的接口。即,您应该首先弄清楚世界所看到的(公共事物),然后弄清楚要实现这一目标需要哪些私人事物。

其他语言很难使曾经公开的语言私有化。也就是说,如果我将变量设为私有或受保护,则会破坏很多代码。但是对于python中的属性,情况并非如此。相反,即使重新排列内部数据,我也可以保持相同的接口。

_和__之间的区别是python实际上试图强制后者。当然,它并不会很努力,但确实会增加难度。拥有_只是告诉其他程序员意图是什么,他们可以无视自己的危险。但是忽略该规则有时会有所帮助。例子包括调试,临时黑客攻击以及使用非您打算使用的第三方代码的方式。


6

对此已经有很多好的答案,但是我将提供另一个答案。这也是对一直说双下划线不是私密的(实际上是私密的)人们的回应。

如果您查看Java / C#,则它们都具有private / protected / public。所有这些都是编译时构造。它们仅在编译时强制执行。如果要在Java / C#中使用反射,则可以轻松访问私有方法。

现在,每次您在Python中调用函数时,本质上都是在使用反射。这些代码在Python中是相同的。

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

对于后面的代码,“点”语法只是语法糖。主要是因为仅使用一个函数调用就已经很难使用getattr。从那里变得更糟。

所以就没有了会有Java / C#版本的private,因为Python不会编译代码。Java和C#无法在运行时检查函数是私有的还是公共的,因为该信息已消失(并且不知道从何处调用该函数)。

现在,有了这些信息,双重下划线的名称修饰对于实现“私密性”最有意义。现在,当从“ self”实例调用函数时,它注意到它以“ __”开头,它只是在此处执行名称修改。它只是语法上的糖。该语法糖允许仅使用反射进行数据成员访问的语言中的“私有”等价物。

免责声明:我从没听过Python开发人员说过这样的话。缺乏“私有”的真正原因是文化上的,但是您还将注意到大多数脚本/解释语言没有私有。除了编译时,严格执行的私有方法在任何情况下都不可行。


4

第一:为什么要隐藏数据?为什么这么重要?

大多数时候,您并不是真的想这样做,但是您这样做是因为其他人正在这样做。

如果您确实真的不希望别人使用某些东西,请添加一个在其前面下划线。就是这样... Pythonistas知道带有下划线的内容并不能保证每次都能正常工作,并且可能在您不知情的情况下发生变化。

那就是我们的生活方式,我们对此表示满意。

使用两个下划线会使您的类难以继承,甚至您也不想那样工作。


2
您省略了双下划线不利于子类化的原因……这将改善您的答案。
Matt Joiner

2
鉴于双下划线确实只是为了防止名称与子类生成器发生冲突(也就是说,“递给”子类生成器),所以我不知道名称处理是如何造成问题的。
亚伦·霍尔

4

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

如果在模块级别将方法转换为函数,则将消除子类覆盖它的机会。将某些功能移至模块级别比使用隐藏名称修饰的方法更具Python风格。


3

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

  • 两个下划线(__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

打印测试对象的所有有效属性

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

希望答案对您有所帮助:)


2

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

在Java中,您将所有不应在外部访问的变量设为私有。在Python中,由于没有“私密性”,因此您无法实现这一目标(正如Python原则之一所说的:“我们都是成年人”)。因此,双下划线仅表示“伙计,请勿直接使用此字段”。相同的含义是下划线,当您必须从所考虑的类继承时,这不会引起任何头痛(这只是双下划线可能引起的问题的一个示例)。

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


对于“专用”使用双下划线,对于“受保护”使用单下划线。通常,人们对所有内容都使用单个下划线(双下划线将有助于强制执行私有性,这通常与Python风格背道而驰)。
乔纳森·斯特恩伯格

1
但是,这是否会使两个下划线类似于私有,而一个下划线类似于protected?为什么不从“私有”开始呢?
Paul Manta

@Paul不,不是。Python中没有私有的东西,您不应该尝试实现它。
Roman Bodnarchuk 2011年

@Roman从概念上来讲...请注意“ private”周围的引号。
保罗·曼塔

1

“如果不确定变量是私有变量还是受保护变量,最好选择私有变量。” -是的,在Python中也是如此。

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

如有疑问,请选择非公开;稍后将其公开比将公共属性设为不公开要容易。

其他答案也考虑了公共和私有之间的区别以及Python中的名称修饰。从同一个链接,

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.