我应该在 Python 中使用名称修改吗?

Should I use name mangling in Python?

提问人:Paul Manta 提问时间:9/18/2011 最后编辑:brandizziPaul Manta 更新时间:10/13/2022 访问量:63633

问:

在其他语言中,有助于生成更好代码的一般准则始终是尽可能隐藏所有内容。如果不确定变量应该是私有的还是受保护的,最好使用 private。

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

如果惯例只使用一个下划线,我也想知道其中的基本原理。

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

我来自一些语言,这些语言训练你认为一切都应该只在需要的时候公开,而不是更多。理由是这将减少依赖关系,并使代码更安全地更改。Python的逆向做事方式——从公共开始,走向隐藏——对我来说很奇怪。

Python 命名约定

评论


答:

239赞 brandizzi 9/18/2011 #1

如有疑问,请将其保留为“公共”——我的意思是,不要添加任何内容来掩盖您的属性名称。如果你有一个具有某种内部值的类,请不要为它烦恼。而不是写:

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 具有属性

如今,大多数面向对象语言都使用相反的方法:不应该使用的东西不应该是可见的,所以属性应该是私有的。从理论上讲,这将产生更易于管理、耦合更少的类,因为没有人会鲁莽地更改对象的值。

然而,事情并没有那么简单。例如,Java 类有许多只获取值的 getter 设置值的 setter。比方说,你需要七行代码来声明一个属性——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

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

(另请参阅 Python 不是 Java这篇关于使用 getter 和 setter 的危害的文章

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

即使在具有私有属性的语言中,您也可以通过一些反射/内省库来访问它们。人们在框架中和解决紧急需求时经常这样做。问题在于,内省库只是一种复杂的方法,可以对公共属性执行操作。

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

问题是无法看到 - 它被要求看到

对于 Pythonista 来说,封装不是无法看到类的内部结构,而是避免查看它的可能性。封装是组件的属性,用户可以在不考虑内部细节的情况下使用它。如果你可以使用一个组件而不用担心它的实现,那么它就是封装的(在Python程序员看来)。

现在,如果你写了一个类,你可以使用它而不考虑实现细节,如果你出于某种原因查看类内部,也没有问题。关键是:你的 API 应该是好的,剩下的就是细节了。

圭多如是说

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

这就是文化

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

既然已经有这种文化,建议你遵循它。否则,当你在 Stack Overflow 中提问时,Python 程序员会告诉你从代码中删除 :)__

评论

4赞 Jupiter 2/28/2020
1. 封装用于保护类不变量。不要向外界隐瞒不必要的细节,因为这会很烦人。2. “关键是:你的 API 应该是好的,剩下的就是细节了。”这是真的。公共属性是 API 的一部分。此外,有时公共 setter 是合适的(关于你的类不变量),有时则不合适。具有不应公开的公共 setter(违反不变量的风险)的 API 是一个糟糕的 API。这意味着无论如何你都必须考虑每个 setter 的可见性,而拥有“默认值”意义不大。
0赞 chepner 1/7/2023
这甚至不是默认设置:Python 没有不同的可见性模式。(如果你真的愿意,即使是名称篡改也很容易被击败;它的目的是避免无意中覆盖继承的属性。
2赞 Roman Bodnarchuk 9/18/2011 #2

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

在 Java 中,您将所有不应该在外部访问的变量设为私有。同时,在 Python 中,你无法做到这一点,因为没有“隐私”(正如 Python 原则之一所说——“我们都是成年人”)。所以双下划线只意味着“伙计们,不要直接使用这个字段”。相同的含义具有单下划线,当您必须从所考虑的类继承时,这同时不会引起任何头痛(只是双下划线可能导致的问题的示例)。

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

评论

0赞 Jonathan Sternberg 9/18/2011
对“private”使用双下划线,对“protected”使用单下划线。通常,人们只对所有内容使用单下划线(双下划线将有助于强制执行隐私性,这通常与 Python 风格相悖)。
1赞 Paul Manta 9/18/2011
但是,这难道不是使两个下划线类似于私有,一个下划线类似于受保护吗?为什么不从“私人”开始呢?
0赞 Roman Bodnarchuk 9/18/2011
@Paul 不,它没有。Python 中没有私有,你不应该尝试实现它。
0赞 Paul Manta 9/18/2011
@Roman 从概念上讲......注意“private”周围的引号。
17赞 Matt Joiner 9/18/2011 #3

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

一个更好的解决方案是 Python 鼓励的路线:你的类和变量应该有很好的文档记录,并且它们的行为应该很清楚。源应该可用。这是编写代码的更具可扩展性和可靠性的方法。

我在 Python 中的策略是这样的:

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

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

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

5赞 JBernardo 9/18/2011 #4

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

大多数时候,你并不是真的想这样做,但你这样做是因为别人在做。

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

这就是我们的生活方式,我们对此感到满意。

使用两个下划线会让你的类变得如此糟糕,以至于即使你也不想以这种方式工作。

评论

2赞 Matt Joiner 9/18/2011
您省略了双下划线不利于子类化的原因......这将改善您的答案。
2赞 Russia Must Remove Putin 1/30/2017
鉴于双下划线实际上只是为了防止与子类发生名称冲突(作为对子类使用者说“放手”的一种方式),我看不出名称修改如何产生问题。
10赞 Winston Ewert 9/18/2011 #5

您不应该从私有数据开始,并在必要时将其公开。相反,您应该从弄清楚对象的界面开始。也就是说,你应该首先弄清楚世界看到了什么(公共的东西),然后弄清楚哪些私人的东西是发生这种情况所必需的。

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

_ 和 __ 之间的区别在于,python 实际上试图强制执行后者。当然,它并不努力,但它确实使它变得困难。仅仅告诉其他程序员意图是什么,他们就可以自由地忽略,后果自负。但忽略这条规则有时是有帮助的。示例包括调试、临时黑客攻击以及使用不打算按使用方式使用的第三方代码。

7赞 Jonathan Sternberg 9/18/2011 #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 开发人员中的任何人说过这样的话。缺乏“私有”的真正原因是文化,但你也会注意到,大多数脚本/解释语言都没有私有。除了编译时之外,严格可强制执行的私有在任何事情上都是不切实际的。

5赞 Tanner_Wauchope 9/21/2015 #7

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

如果在模块级别将方法转换为函数,则子类将失去重写它的机会。将某些功能移动到模块级别比尝试使用名称修改隐藏方法更像 Pythonic。

36赞 Russia Must Remove Putin 1/20/2016 #8

首先 - 什么是名称篡改?

当您处于类定义中并使用 or 时,将调用名称修改,即两个(或多个)前导下划线,最多一个尾随下划线。__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 可替换的,并且没有想到我发现它有用的示例

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

我个人的做法是故意避免它。我在一个非常大的代码库上工作。它的罕见用途像拇指酸痛一样突出,似乎没有道理。

你确实需要意识到它,所以当你看到它时你就知道它。

政治人物 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 的人有时很难理解当他们无法手动访问他们在类定义中看到的名称时发生了什么。这不是反对它的有力理由,但如果你有学习的观众,这是需要考虑的事情。

一个下划线?

如果惯例只使用一个下划线,我也想知道其中的基本原理。

当我的意图是让用户远离某个属性时,我倾向于只使用一个下划线,但那是因为在我的心智模型中,子类人可以访问该名称(他们总是拥有,因为他们可以很容易地发现残缺不全的名称)。

如果我正在审查使用前缀的代码,我会问他们为什么要调用名称修改,如果他们不能用一个下划线做得很好,请记住,如果子类为类和类属性选择相同的名称,尽管如此,还是会发生名称冲突。__

5赞 Nitish Chauhan 4/15/2018 #9

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

  • 两个前导下划线 (__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 中被称为“Name Mangling”。 您可以像这样访问它:

testObj2 = Test()
print testObj2._Test__a

test1

同样,在_a的情况下,变量只是为了通知开发人员它应该用作该类的内部变量,即使你访问它,python 解释器也不会做任何事情,但这不是一个好的做法。

testObj3 = Test()
print testObj3._a

test2

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

testObj4 = Test()
print testObj4.a

test3

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

2赞 Yaroslav Nikitenko 8/18/2019 #10

“如果对变量应该是私有的还是受保护的有疑问,最好选择私有。” - 是的,在 Python 中也是如此。

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

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

公共和私有之间的区别,以及 Python 中的名称修改已经在其他答案中考虑过。从同一链接,

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

-7赞 jai ganesh 8/19/2020 #11

#EXAMPLE PROGRAM FOR 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_']

评论

3赞 rayryeng 8/19/2020
这根本没有回答问题 - 它显示了一个例子,但它没有触及实际问题的核心。这个问题已经快 9 年了,答案被接受。这是否为此处已经提供的答案增加了任何内容?