【问题标题】:Making a method private in a python subclass在 python 子类中将方法设为私有
【发布时间】:2010-10-01 21:43:39
【问题描述】:

是否可以在子类中将公共方法设为私有?我不希望扩展这个类的其他类能够调用某些方法。这是一个例子:

class A:
    def __init__(self):
        #do something here

    def method(self):
        #some code here

class B(A):
    def __init__(self):
        A.__init__(self)
        #additional initialization goes here

    def method(self):
        #this overrides the method ( and possibly make it private here )

从现在开始,我不希望任何从 B 扩展的类能够调用 method。 这可能吗?

编辑:这样做的“逻辑”原因是我不希望用户以错误的顺序调用方法。

【问题讨论】:

    标签: python oop inheritance


    【解决方案1】:

    与此主题的流行时尚相反,有合理的理由区分公共、私有和受保护的成员,无论您是在 Python 还是更传统的 OOP 环境中工作。很多时候,您会在某种对象专业化水平上为特别冗长的任务开发辅助方法。不用说,您真的不希望任何子类继承这些方法,因为它们在专门的上下文中没有意义,甚至不应该是可见的;然而它们是可见的,它们减少了诸如标签补全、对象导航器和其他系统软件之类的东西的实用性,因为所有不同抽象级别的东西都被扁平化并组合在一起。请注意,这些编程辅助工具并非微不足道。如果您是一名学生并且仅仅因为您正在学习如何做一百万次,那么它们只是微不足道的。

    Python 在历史上的发展方式是,由于意识形态惯性和兼容性问题,实现公有/私有区分变得越来越困难。这是显而易见的事实。改变他们一直在做的事情对每个人来说都是一件非常头疼的事情。因此,我们现在有一百万的 Python 粉丝,他们都读过相同的一两篇原创文章,明确地认为公共/私人的区别是“非 Python 的”。这些人,由于缺乏批判性思维或对广泛的普遍做法的公平性,立即利用这个机会来积累大量可预测的辩护词——De Defensione Serpentis——我怀疑这不是来自理性选择via pythonis(pythonic的方式),而忽略了其他语言,要么选择不使用,要么不熟练使用,要么因为工作而无法使用。

    正如有人已经说过的,在 Python 中产生类似于私有方法的效果的最佳方法是在方法名称前加上 __(两个下划线)。另一方面,实际上这样做的唯一一件事就是在对象的__dict__ 中插入一个变形的属性名称。例如,假设您有以下类定义:

    class Dog(object):
        def __bark(self):
            print 'woof'
    

    如果你运行dir(Dog()),你会看到一个奇怪的成员,叫做_Dog__bark。事实上,这个技巧存在的唯一原因是为了规避我之前描述的问题:即防止继承、重载和替换超级方法。

    希望将来会有一些私有方法的标准化实施,当人们意识到组织不需要使用单个细胞复制 DNA 的方法时,有意识的头脑需要不断地弄清楚如何修复它的组织和内脏。

    【讨论】:

    • 说得好! (只是我的 $.02 来满足 stackoverflow 字数统计。)
    • 一个很好的咆哮。但这并没有降低这个想法的合法性,即类或任何代码的用户(而不是创建者)最终应该有权决定使用什么以及如何使用它。如果要警告用户,请在名称前加一个下划线;此外,这可以防止混乱的标签完成。
    • 我需要将一个方法设为私有,因为我有两个名称相似的方法,但只能从类外部调用一个。另一种是更方便的方法,使类内部的事情变得更容易。我只是不想不小心误打错电话。也许我的代码可以重新设计为更加“Pythonic”,但这是一项艰巨的工作。我累了,只想今晚工作。 :-)
    【解决方案2】:

    没有办法在 Python 中真正做到这一点。相当不合情理,它是。

    正如 Guido 所说,我们在这里都是成年人。

    这是一个很好的summary of the philosophy behind everything in Python being public

    【讨论】:

      【解决方案3】:

      您可以使用单下划线或双下划线作为方法和成员的前缀。单下划线意味着“请不要使用我,我应该只被这个类使用”,双下划线指示 Python 编译器将方法/成员名称与类名混淆;只要类及其子类不具有相同的名称,方法/成员就可以被认为是“私有的”。

      但是,到目前为止,您的要求的解决方案是编写清晰的文档。如果您不希望您的用户以错误的顺序调用方法,请在文档中说明。

      毕竟,即使是 C++ 私有也不是那么私有。例如想想老把戏:

      #define private public
      #include <module>
      

      【讨论】:

        【解决方案4】:

        我很惊讶没有人提到这一点,但是在方法名称前加上一个下划线是将其标记为“私有”的正确方法。当然,它并不是真正的私人,(如其他答案中所述),但你去吧。

        def _i_am_private(self):
            """If you call me from a subclass you are a naughty person!"""
        

        【讨论】:

        • 我一直认为它是两个下划线。您对此有引用吗?
        【解决方案5】:

        Python 作为源代码分发。私有方法的想法本身就没有什么意义。

        想要扩展B 的程序员因隐私问题而受挫,查看B 的源代码,将method 的源代码复制并粘贴到子类C 中。

        您通过“隐私”获得了什么?您所能期望的最好的结果就是让您的潜在客户无法进行复制和粘贴。

        在最坏的情况下,他们会丢弃您的包,因为他们无法扩展它。

        是的,所有开源都以一种或另一种方式扩展。您无法预见您编码的所有内容和所有用途。当代码作为源代码分发时,很难防止将来使用。

        How do I protect Python code?


        编辑关于“白痴”代码。

        首先,python 90% 的时间都是作为源代码分发的。因此,任何下载、安装、然后拒绝阅读 API 指南并乱序调用方法的白痴仍然可以找出问题所在。

        我们有三类白痴。

        • 拒绝阅读 API 指南(或略读并忽略相关部分)并且不顾文档地调用方法的人。您可以尝试将某些内容设为私有,但这无济于事,因为他们会做错其他事情——并抱怨它。 [我不会说出名字,但我曾与那些似乎花了很多时间不正确地调用 API 的人一起工作。另外,你会在 SO 上看到这样的问题。]

          您只能帮助他们提供可以剪切和粘贴的工作代码示例。

        • 对 API 感到困惑并以您能想象到的各种不同方式调用方法的人(有些是您无法想象的。)您可以尝试将某些东西设为私有,但他们永远无法获得 API。

          您只能通过提供工作代码示例来帮助他们;即使这样,他们也会错误地剪切和粘贴。

        • 那些拒绝你的 API 并想要重写它以使其成为“白痴证明”的人。

          您可以向他们提供一个有效的代码示例,但他们不喜欢您的 API 并且会坚持重写它。他们会告诉你,你的 API 很疯狂,而且他们已经改进了。

          您可以让这些人参与不断升级的“防白痴”军备竞赛。你把所有东西放在一起,它们就会分开。

        此时,隐私为您做了什么?有些人会拒绝理解它;有些人对此感到困惑;有些人想解决这个问题。

        public 怎么样,让那些被你称为“白痴”的人从你的代码中学习?

        【讨论】:

        • 你的论点不是针对 Python 中的私有成员;他们一般反对信息隐藏。为什么信息隐藏在 C++ 和 Java 中有意义,但在 Python 中没有意义?
        • Pythonist 可以提供的最佳答案通常是“因为那不会是 Pythonic”,或者“因为 Guido 会说……”。但这些答案引出了问题。
        • 确实,真正的问题是,信息隐藏有用吗? 如果没有,C++ 和 Java 无缘无故地搞砸了。如果是的话,我认为,Python 中缺少一些东西。
        • 信息隐藏是文档。通过从 API 中删除 crud,API 变得更容易学习。使用 150,000 个方法编写代码,其中没有一个是私有的,然后看看你是否仍然想称人们为白痴。
        • 这是一个非常天真的答案。一个不好的假设是私有方法只需要避免覆盖方法。但是,它通常对 API 使用很有用。私有方法将 API 简化为重要方法;为什么要向最终用户公开无用的辅助方法?如果有的话,应该支持受保护的方法。另一个问题(短视?)是,由于所有内容都是公开的,由于代码的公开性质,在升级库时,您更有可能发生扩展类的名称冲突。封装和抽象是好事。
        【解决方案6】:

        这可能是一个公平的近似值。 “救援”的词法作用域:

        #!/usr/bin/env python
        
        class Foo(object):
            def __init__(self, name):
                self.name = name
                self.bar()
        
            def bar(self):
                def baz():
                    print "I'm private"
                    print self.name
        
                def quux():
                    baz()
        
                self.quux = quux
        
        
        if __name__ == "__main__":
            f = Foo("f")
            f.quux()
        
            g = Foo("g")
            g.quux()
        
            f.quux()
        

        打印:

        I'm private
        f
        I'm private
        g
        I'm private
        f
        

        【讨论】:

          【解决方案7】:

          “一切都必须公开”的支持者认为作者试图向用户隐藏有用的 API。这家伙不想违反一条毋庸置疑的 Python 法则。他想使用一些方法来定义一个有用的 API,并且他想使用其他方法来组织该 API 的实现。如果两者之间没有区别,并不意味着作者不是白痴。这意味着作者懒得真正定义 API。

          在 Python 中,不是将属性或方法标记为私有,而是可以将其前缀为 _ 作为弱的“内部使用”指示符,或者使用 __ 作为稍强的指示符。在模块中,名称可以以相同的方式以_ 为前缀,您还可以将构成模块公共API 的字符串序列放入名为__all__ 的变量中。

          愚蠢的一致性是小脑袋里的妖精。

          【讨论】:

          • 另见 pypi 上可用的新 'apipkg'
          【解决方案8】:

          将继承的方法从公共更改为私有会破坏继承。具体来说,它打破了 is-a 关系。

          想象一个带有开门公共方法的餐厅类(即,现在是晚餐时间,我们想将前门标志从关闭状态翻转为打开状态)。现在我们想要一个餐饮类,它可以共享餐厅的许多实施细节(他们都需要厨师、厨房、菜肴、食品供应商,甚至可能是服务员),但没有用餐区、前门或敞开的门方法。从餐厅继承是错误的!看起来您需要做的只是将 open-doors 方法更改为私有方法,因此没有人可以使用它,但是“任何 Catering 对象都是餐厅”是错误的,因为餐厅的公共接口的一部分是开门的。最好通过添加一个新的基类来重构 Restaurant,然后将 Restaurant 和 Catering 都派生自它。

          具有受保护或私有继承概念的语言仅支持这种用于实现的继承思想,但 Python 不是其中之一。 (在那些语言中也没有用,除了很少。)通常当寻求非公共继承时,包含(又名“有关系”)是更好的路径,您甚至可以将属性设置为受保护或私有。

          Python 拼写用单前导下划线保护,私有用双前导下划线保护,修改了 Triptych 的回答中提到的“成人同意”哲学。类的“危险”属性和方法,例如可能导致数据丢失的,应该是非公开的(是否使它们受保护或私有受其他因素影响),使用公共方法提供更简单、更安全的接口。

          【讨论】:

            【解决方案9】:

            要使方法成为私有方法,您可以像这样命名-mangle 方法:

            class Foo:
                def __blah():
                    pass
            
            class Bar(Foo):
                def callBlah():
                    self.__blah() # will throw an exception
            

            但是如果子类真的想的话,他们仍然可以通过自省找到你的方法。

            但是 Python(经过深思熟虑的设计和选择)没有私有成员的概念。

            【讨论】:

            • 用 getattr/setattr 替换方法怎么样?那行得通吗?即使我失去了方法......
            • 我认为你可以替换属性,以便他们调用类似:subclassInstance.foo(),它会抛出异常。但我认为他们仍然可以调用 SuperClass.foo(subclassInstance) 并且仍然可以使用它。可能还有更多的元编程魔法,但据我所知没有。
            【解决方案10】:

            即使在同意的成年人中,您也有一些理由不希望将方法公开给派生类。问题是,我是否可以有一个像 Parent > Child 这样的层次结构,其中用户可以从 Parent 或 Child 派生,但不会暴露任何私有或受保护的方法。在以下示例中, Child 显式重载 Parent.__private 而不暴露它。

            class Parent(object):
                def __private(self):
                    print 'Parent'
            
                def go(self):
                    self.__private()
            
            class Child(Parent):
                def __init__(self):
                    Parent.__init__(self)
                    self._Parent__private = self.__private
            
                def __private(self):
                    print 'Child'
            

            【讨论】:

            • 这些是“隐藏”还是无法访问?如果是这样,拥有它们有什么意义呢?我们是否可以使用Parent 中的方法获取它们并设置/增加它们,而无需将它们更改为任何东西?
            【解决方案11】:
            class MyClass(object): 
                def _prtectedfunc(self):
                    pass # with one undersocre
            
                def __privatefunc(self):
                    pass # with two undersocre
            

            http://linuxwell.com/2011/07/21/private-protected-and-public-in-python/

            【讨论】:

              【解决方案12】:

              这个帖子有很多很好的答案,但从实际的角度来看,这里有一些固执己见的答案:

              确实需要避免来自公共 API、智能感知、文档、import * 等的私有成员的垃圾。因此,请始终将 _ 或 __ 附加到这些成员。

              最大的问题:应该是 _ 还是 __?纯粹的 OOP 观点是始终将 __ 附加到私有成员,并将 _ 附加到受保护的方法。 __ 确实进行了名称修改,并且难以使用诸如 setattrib、测试框架之类的东西并降低了可读性。所以我个人的看法是尽量避免使用__,将_用于私有成员。

              但是我们希望派生类使用但不作为公共 API 的受保护成员呢?我的偏好是完全不使用下划线。这允许这些成员进入公共文档。然后我们依靠 docstring 告诉用户这些是受保护的成员,如下所示:

              class Class1:
                def _private1():
                  pass
              
                def protected1():
                  """(for derived classes) Does XYZ."""
                  pass
              
                def public1()
                  pass
              

              【讨论】:

                【解决方案13】:

                这个帖子有很多很好的答案,但从实际的角度来看,这里有一些固执己见的答案:

                您确实需要从公共 API 中隐藏(也称为文档)低级变量和方法。因此,请始终将 _ 或 __ 附加到这些成员。更大的问题:应该是_还是__?纯粹的 OOP 观点是始终将 __ 附加到私有成员和 _ 附加到“受保护”方法(即可访问,可从派生类覆盖)。 __ 确保派生类不能覆盖它。另一方面,它也使setattr、可测试性和可读性等内容的使用变得困难。

                我个人的观点(以及Google's style guide)是尽量避免使用__,将_用于私人会员。

                但是受保护的成员呢?我们如何告诉我们的图书馆用户他们必须/应该覆盖什么?在这里,我不喜欢对受保护的成员使用下划线,而是在基类中依赖文档字符串 cmets 和可选的@abstractmethod。这里的一个优点是,受保护的成员不会从各种文档生成器中删除。

                【讨论】:

                  猜你喜欢
                  • 2022-06-11
                  • 2013-03-16
                  • 2010-11-25
                  • 2014-11-22
                  • 2011-01-23
                  • 1970-01-01
                  • 2016-08-08
                  • 2020-09-11
                  • 2021-12-29
                  相关资源
                  最近更新 更多