【问题标题】:Python name manglingPython 名称修改
【发布时间】:2011-11-19 09:15:24
【问题描述】:

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

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

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

这是我在JBernardo's answer 上留下的评论。它解释了我问这个问题的原因以及为什么我想知道 Python 与其他语言不同的原因:

我的语言训练你认为一切都应该只在需要时公开,而不是更多。原因是这将减少依赖性并使代码更安全地更改。 Python 的反向做事方式——从公开到隐藏——对我来说很奇怪。

【问题讨论】:

    标签: python naming-conventions


    【解决方案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 的老家伙也鄙视这个默认值 - 但无论如何它是默认值,所以我建议你遵循它,即使你觉得不舒服。

    如果您真的想发送消息“不能碰这个!”对于您的用户,通常的方法是在变量前加上 one 下划线。这只是一个约定,但人们理解它并在处理此类事情时要格外小心:

    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
    

    双下划线呢?好吧,我们主要使用双下划线魔术to avoid accidental overloading of methods and name conflicts with superclasses' attributes。如果您编写一个要多次扩展的类,这将非常有价值。

    如果你想将它用于其他目的,你可以,但不常用也不推荐。

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

    Python 有属性

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

    然而,事情并非如此简单。例如,Java 类有许多只get设置 值的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
    

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

    (另请参阅Python is not Javathis article about the harms of using getters and setters。)。

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

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

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

    无法看到问题 - 需要才能看到

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

    现在,如果您编写了一个类,您可以在不考虑实现细节的情况下使用它,如果您出于某种原因想要查看该类的内部没有问题。重点是:你的API要好,剩下的就是细节了。

    圭多是这么说的

    好吧,这没有争议:he said so, actually。 (寻找“开放式和服”。)

    这就是文化

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

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

    【讨论】:

    • 1.封装是为了保护类不变量。不要向外界隐藏不必要的细节,因为这会很烦人。 2.“重点是:你的API要好,剩下的就是细节。”这是真实的。公共属性是 API 的一部分。此外,有时公共设置器是合适的(关于你的类不变量),有时它们不是。具有不应公开的公共设置器(违反不变量的风险)的 API 是一个糟糕的 API。这意味着无论如何您都必须考虑每个 setter 的可见性,并且具有“默认值”意味着更少。
    【解决方案2】:

    首先 - 什么是名称修饰?

    当您在类定义中并使用__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 文档中的 example 不是 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 新手有时会难以理解发生了什么。这不是反对它的强烈理由,但如果您有学习的受众,则需要考虑这一点。

    一个下划线?

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

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

    如果我正在审查使用 __ 前缀的代码,我会问他们为什么要调用名称修饰,以及如果他们不能使用单个下划线来做同样的事情,请记住,如果子类选择类和类属性的名称相同,尽管如此,仍然会发生名称冲突。

    【讨论】:

      【解决方案3】:

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

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

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

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

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

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

      【讨论】:

        【解决方案4】:

        您不应该从私人数据开始,并在必要时将其公开。相反,您应该从弄清楚对象的接口开始。 IE。你应该首先弄清楚世界看到了什么(公共的东西),然后弄清楚什么是私人的东西是必要的。

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

        _ 和 __ 之间的区别在于,python 实际上试图强制执行后者。当然,它并没有真正努力,但它确实使它变得困难。拥有 _ 只是告诉其他程序员意图是什么,他们可以随意忽略,后果自负。但忽略该规则有时会有所帮助。示例包括调试、临时 hack 以及使用不打算以您使用它的方式使用的第三方代码。

        【讨论】:

          【解决方案5】:

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

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

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

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

          “点”语法只是后一段代码的语法糖。主要是因为使用 getattr 已经很难看,只有一个函数调用。从那里开始变得更糟。

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

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

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

          【讨论】:

            【解决方案6】:

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

            如果您在模块级别将方法转换为函数,则子类将无法覆盖它。将某些功能移至模块级别比尝试使用名称修饰隐藏方法更具 Pythonic。

            【讨论】:

              【解决方案7】:

              以下代码 sn-p 将解释所有不同的情况:

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

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

              【讨论】:

                【解决方案8】:

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

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

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

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

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

                【讨论】:

                • 您省略了双下划线对子类化不利的原因...这将改善您的答案。
                • 鉴于双下划线实际上只是为了防止与子类的名称冲突(作为对子类的一种说法,“放手”),我看不出名称修饰如何产生问题。
                【解决方案9】:

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

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

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

                【讨论】:

                • 对“private”使用双下划线,对“protected”使用单下划线。通常,人们对所有内容都使用单下划线(双下划线有助于加强隐私,这通常与 Python 风格相悖)。
                • 但这不是让两个下划线类似于 private 和一个下划线类似于 protected 吗?为什么不从“私人”开始?
                • @Paul 不,它没有。 Python 中没有 private,你不应该尝试实现它。
                • @Roman 从概念上讲...注意“私人”周围的引号。
                【解决方案10】:

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

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

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

                在其他答案中已经考虑了公共和私有之间的区别,以及 Python 中的 name mangling。来自同一个链接,

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

                【讨论】:

                  【解决方案11】:

                  #EXAMPLE PROGRAM FOR Python name mangling

                  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_']
                  

                  【讨论】:

                  • 这根本没有回答问题 - 它显示了一个示例,但没有触及实际问题的核心。那个和这个问题已经有将近 9 年的历史了,答案是可以接受的。这对这里已经提供的答案有什么补充吗?
                  猜你喜欢
                  • 1970-01-01
                  • 2011-08-23
                  • 2021-10-20
                  • 2011-06-07
                  • 2011-07-11
                  • 1970-01-01
                  相关资源
                  最近更新 更多