关于命名约定:Python名称管理

Python name mangling

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

Python也是这样吗?首先,我应该在所有内容上使用两个前导下划线,并且只在需要时减少隐藏(只有一个下划线)吗?

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

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

I come from languages that train you to think everything should be only as public as needed and no more. The reasoning is that this will reduce dependencies and make the code safer to alter. The Python way of doing things in reverse -- starting from public and going towards hidden -- is odd to me.


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

1
2
3
4
5
6
7
class Stack(object):

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

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

默认情况下写入:好的。

1
2
3
4
5
6
7
class Stack(object):

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

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

这无疑是一种有争议的做事方式。python新手讨厌它,甚至一些老python家伙也鄙视这个默认值——但无论如何,它都是默认值,所以我真的建议您遵循它,即使您觉得不舒服。好的。

如果你真的想发送信息"不能碰这个!"对于您的用户,通常的方法是在变量前面加一个下划线。这只是一个惯例,但是人们理解它,并且在处理这些事情时要加倍小心:好的。

1
2
3
4
5
6
7
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)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 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类有很多属性和吸收器,它们只获取设置值的值和设置器。比如说,您需要七行代码来声明一个属性——Python程序员会说这是不必要的复杂。而且,在实践中,您只需编写这整批代码来获得一个公共字段,因为您可以使用getter和setter更改其值。好的。

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

1
person.age = age;

在你的代码中,让我们说,好的。

1
person.setAge(age);

setAge()为:好的。

1
2
3
4
5
6
7
public void setAge(int age) {
    if (age >= 0) {
        this.age = age;
    } else {
        this.age = 0;
    }
}

因此,在Java(和其他语言)中,默认是使用GETTER和SETTER,因为它们可能很烦人,但是如果您发现自己处于我所描述的情况下,可以节省大量的时间。好的。

但是,您不需要在Python中这样做,因为Python具有属性。如果你有这门课:好的。

1
2
3
4
 class Person(object):
     def __init__(self, name, age):
         self.name = name
         self.age = age

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 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应该是好的,其余的是详细信息。好的。Guido这样说

嗯,这不是有争议的:事实上,他是这么说的。(寻找"开放和服"。)好的。这就是文化

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

既然已经有了这种文化,我们建议你遵循它。否则,当您在堆栈溢出中问一个问题时,Python程序员会告诉您从代码中删除__,这会让您很恼火。好的。好啊。


我不会说实践产生更好的代码。可见性修饰符只会分散您对手头任务的注意力,作为一种副作用,会强制您的接口按预期使用。一般来说,如果程序员没有正确地阅读文档,加强可见性可以防止他们把事情搞得一团糟。

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

我在python中的策略是:

  • 只需写下该死的东西,不要对数据应该如何受到保护做任何假设。这假设您编写代码是为了为您的问题创建理想的接口。
  • 对于那些可能不会在外部使用,并且不属于正常"客户机代码"接口的内容,请使用前导下划线。
  • 只对类内纯粹方便的内容使用双下划线,或者如果意外暴露,会造成相当大的损坏。
  • 最重要的是,应该清楚每件事都在做什么。如果有人要使用它,请记录下来。如果你想在一年内有用,就把它记录下来。

    作为补充说明,您实际上应该使用其他语言进行保护:您永远不知道您的类稍后可能会被继承,以及它可能被用于什么目的。最好只保护那些您确定不能或不应该被外部代码使用的变量。


    第一,什么叫曼格林?

    在类定义中使用__any_name__any_name_时,即两个(或多个)前导下划线和最多一个尾随下划线,将调用名称管理。

    1
    2
    3
    class Demo:
        __any_name ="__any_name"
        __any_other_name_ ="__any_other_name_"

    现在:

    1
    2
    3
    4
    5
    6
    >>> [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_'

    When in doubt, do what?

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

    一个潜在的值是避免与想要重写行为的子类发生名称冲突,这样父类功能就可以按预期继续工作。但是,在Python文档中的示例不是liskov可替换的,在我发现它有用的地方没有想到任何示例。

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

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

    你需要意识到它,这样当你看到它的时候你就会知道它。

    PEP 8

    《python标准库风格指南》PEP8目前称(节略):

    There is some controversy about the use of __names.

    If your class is intended to be subclassed, and you have attributes that you do not want subclasses to use, consider naming them with double leading underscores and no trailing underscores.

  • Note that only the simple class name is used in the mangled name, so if a subclass chooses both the same class name and attribute name,
    you can still get name collisions.

  • Name mangling can make certain uses, such as debugging and __getattr__() , less convenient. However the name mangling algorithm is well documented and easy to perform manually.

  • Not everyone likes name mangling. Try to balance the need to avoid accidental name clashes with potential use by advanced callers.

  • 它是如何工作的?

    如果在一个类定义中预先加上两个下划线(不加结束双下划线),则该名称将被破坏,并且在对象上预先加上一个下划线,后跟类名:

    1
    2
    3
    4
    5
    6
    7
    >>> class Foo(object):
    ...     __foobar = None
    ...     _foobaz = None
    ...     __fooquux__ = None
    ...
    >>> [name for name in dir(Foo) if 'foo' in name]
    ['_Foo__foobar', '__fooquux__', '_foobaz']

    请注意,只有在分析类定义时,名称才会损坏:

    1
    2
    3
    4
    5
    6
    >>> 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不熟悉的人有时很难理解发生了什么,因为他们无法手动访问在类定义中定义的名称。这不是一个强烈反对它的理由,但是如果你有一个学习型的听众,这是需要考虑的。

    一个下划线?

    If the convention is to use only one underscore, I'd also like to know the rationale.

    当我的意图是让用户不接触属性时,我倾向于只使用一个下划线,但这是因为在我的心理模型中,子类可以访问这个名称(它们总是拥有这个名称,因为它们可以很容易地发现被损坏的名称)。

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


    您不应该从私有数据开始,并在必要时将其公开。相反,您应该首先计算出对象的接口。也就是说,你应该先弄清楚世界上看到了什么(公共的东西),然后弄清楚什么是私人的东西才是实现这一目标的必要条件。

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

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


    已经有很多很好的答案了,但我会再提供一个。这在一定程度上也是对那些一直说双下划线不是私有的人的回应(实际上是这样)。

    如果你看爪哇/ C,他们都有私人的/保护的/公开的。所有这些都是编译时构造。它们只在编译时执行。如果您在爪哇/C中使用反射,您可以轻松地访问私有方法。

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

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

    "点"语法只是后一段代码的语法甜头。主要是因为使用getattr只需要一个函数调用就已经很难看了。从那以后情况就更糟了。

    因此,Python不编译代码,就不可能有一个私有的Java/C版本。Java和C语言不能在运行时检查函数是私有的还是公共的,因为该信息已经丢失(并且它不知道函数从哪里调用)。

    现在,有了这些信息,双下划线的名称混乱对于实现"私密性"是最有意义的。现在,当从"self"实例调用一个函数并注意到它以"uuuuu"开头时,它只会在那里执行名称管理。这只是更多的句法糖分。在一种只使用反射进行数据成员访问的语言中,这种语法糖分允许相当于"private"。

    免责声明:我从未听过来自Python开发的任何人这样说。缺少"private"的真正原因是文化上的,但是您也会注意到大多数脚本/解释语言都没有private。严格可执行的private除了编译时之外,在任何方面都不实际。


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

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

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

    这就是我们的生活方式,我们同意。

    使用两个下划线会使类的子类化变得非常糟糕,甚至连您都不想这样工作。


    所选的答案很好地解释了属性如何消除对私有属性的需求,但我也会添加模块级的函数,以消除对私有方法的需求。

    如果您在模块级别将一个方法转换成一个函数,您将删除子类重写它的机会。将某些功能移到模块级比试图隐藏名称有误的方法更为简单。


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

    • 两个前导下划线(uuu a)
    • 单前导下划线(_a)
    • 无下划线(A)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      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

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

    1
    2
    3
    4
    5
    6
    testObj1 = Test()
    valid_attributes = dir(testObj1)
    print valid_attributes

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

    在这里,您可以看到uu a的名称已更改为u test_u a,以防止该变量被任何子类覆盖。这个概念在Python中被称为"名称管理"。您可以这样访问:

    1
    2
    3
    4
    testObj2 = Test()
    print testObj2._Test__a

    test1

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

    1
    2
    3
    4
    testObj3 = Test()
    print testObj3._a

    test2

    变量可以从类似于公共类变量的任何地方进行访问。

    1
    2
    3
    4
    testObj4 = Test()
    print testObj4.a

    test3

    希望答案对你有帮助:)


    乍一看,它应该和其他语言一样(在"其他"我指的是Java或C++),但它不是。

    在爪哇,你把所有不能在外面访问的变量私有化。同时,在Python中,由于没有"隐私"(正如其中一个Python原则所说,"我们都是成年人"),所以您无法实现这一点。所以双下划线只表示"伙计们,不要直接使用这个字段"。同样的意思有一个下划线,当您必须从考虑的类继承时,它不会引起任何头痛(只是双下划线可能导致问题的一个例子)。

    所以,我建议您在默认情况下对"private"成员使用单下划线。