【问题标题】:Does Python require intimate knowledge of all classes in the inheritance chain?Python 是否需要对继承链中的所有类有深入的了解?
【发布时间】:2012-07-23 07:03:10
【问题描述】:

Python 类没有公共/私有的概念,因此我们被告知不要触摸以下划线开头的内容,除非我们创建了它。但这不需要我们直接或间接继承的所有类的完整知识吗?证人:

class Base(object):
    def __init__(self):
        super(Base, self).__init__()
        self._foo = 0

    def foo(self):
        return self._foo + 1

class Sub(Base):
    def __init__(self):
        super(Sub, self).__init__()
        self._foo = None

Sub().foo()

预计会在评估 None + 1 时引发 TypeError。所以我必须知道_foo 存在于基类中。为了解决这个问题,可以改用__foo,它通过修改名称来解决问题。这似乎是一个可以接受的解决方案,如果不是优雅的话。但是,如果Base 继承自一个名为Sub 的类(在单独的包中),会发生什么情况?现在我的Sub 中的__foo 覆盖了祖父母Sub 中的__foo

这意味着我必须知道整个继承链,包括每个使用的所有“私有”对象。 Python 是动态类型的这一事实使这变得更加困难,因为没有要搜索的声明。然而,最糟糕的部分可能是Base 现在可能从object 继承,但在未来的某个版本中,它会切换到从Sub 继承。很明显,如果我知道 Sub 是继承自的,我可以重命名我的类,但那很烦人。但我看不到未来。

这不是真正的私有数据类型可以防止问题的情况吗?在 Python 中,如果这些脚趾可能在未来某个时间出现,我如何确定我不会意外踩到某人的脚趾?

编辑:我显然没有明确主要问题。我熟悉名称修饰以及单下划线和双下划线之间的区别。问题是:我如何处理我可能与我现在不知道存在的类发生冲突的事实?如果我的父类(在我没有编写的包中)恰好开始从与我的类同名的类继承,那么即使名称修改也无济于事。我认为这是一个真正的私人成员可以解决的(角落)案例,但 Python 遇到了麻烦,我错了吗?

编辑:根据要求,以下是完整示例:

文件parent.py

class Sub(object):
    def __init__(self):
        self.__foo = 12
    def foo(self):
        return self.__foo + 1
class Base(Sub):
    pass

文件sub.py

import parent
class Sub(parent.Base):
    def __init__(self):
        super(Sub, self).__init__()
        self.__foo = None
Sub().foo()

调用了祖父母的foo,但使用了我的__foo

显然您不会自己编写这样的代码,但parent 可以很容易地由第三方提供,其细节可能随时更改。

【问题讨论】:

  • 您能否发布一个示例来证明这一点,其中另一个同名课程不会完全影响您现有的课程?..或者我仍然不理解..没关系...我做了一个,你的权利我猜这是一个极端情况......只是尽量不要使用我猜的常见私有变量/类名

标签: python subclass private


【解决方案1】:

使用private names(而不是protected),以双下划线开头:

class Sub(Base):
    def __init__(self):
        super(Sub, self).__init__()
        self.__foo = None
        #    ^^

不会与Base 中的_foo__foo 冲突。这是因为 Python 将双下划线替换为单下划线和类名;以下两行是等价的:

class Sub(Base):
    def x(self):
        self.__foo = None # .. is the same as ..
        self._Sub__foo = None

(响应编辑:)类层次结构中的两个类不仅具有相同名称,而且它们都使用相同的属性名称,并且都使用私有重整的机会(__)形式是如此微不足道,以至于在实践中可以安全地忽略它(到目前为止我还没有听说过一个案例)。

然而,从理论上讲,您是正确的,因为要正式验证程序的正确性,人们最了解整个继承链。幸运的是,在任何情况下,形式验证通常都需要一组固定的库。

这是Zen of Python 的精神,其中包括

实用胜于纯粹。

【讨论】:

  • 但这会与假设的Base.__foo 冲突吗?
  • 我认为在你的回答中值得特别提及,因为它更符合 OP 的要求
  • 否,因为双下划线会发生名称修改,但是您可能会执行 self._Base__foo = None 并获得与 OP 中相同的结果(至少我认为修改后的结果是这样的.. .):P
  • 我知道双下划线;我在问题中注意到了它们。问题是当两个同名的类是继承链的一部分时。在那里修改不会有帮助。
【解决方案2】:
  1. 名称修改包括该类,因此您的 Base.__fooSub.__foo 将具有不同的名称。这就是首先将名称修饰功能添加到 Python 的全部原因。一个是_Base__foo,另一个是_Sub__foo

  2. 出于某些原因,许多人更喜欢使用组合 (has-a) 而不是继承 (is-a)。

【讨论】:

  • 这不是错了吗?我认为修改只发生在双下划线而不是单下划线...
  • 我提到了名字修饰;我注意到的特殊情况类似于 A->B->A (第一个 A 显然在一个单独的包中)。现在,祖父类以与我的子类相同的方式修改事物,消除了名称修改所提供的优势。
【解决方案3】:

这意味着我必须了解整个继承链。 . .

是的,你应该知道整个继承链,或者你直接子类化的对象的文档应该告诉你你需要知道什么。

子类化是一项高级功能,应谨慎对待。

threading class

此类表示在单独的控制线程中运行的活动。有两种方法可以指定活动:通过将可调用对象传递给构造函数,或者通过覆盖子类中的run() 方法。不应在子类中覆盖任何其他方法(构造函数除外)。也就是说,只覆盖这个类的__init__()run()方法。

【讨论】:

    【解决方案4】:

    您多久修改一次继承链中的基类,以从与更下游的子类同名的类引入继承???

    不那么轻率,是的,您必须知道您正在使用的代码。毕竟,您当然必须知道所使用的公共名称。 Python 是 Python,发现您的祖先类使用的公共名称与发现私有名称几乎相同。

    在多年的 Python 编程中,我从来没有发现这在实践中是一个很大的问题。当您命名实例变量时,您应该非常清楚 (a) 一个名称是否足够通用,以至于它可能会在其他上下文中使用,以及 (b) 您正在编写的类是否可能涉及与其他未知类的继承层次结构。在这种情况下,您会更仔细地考虑您使用的名称; self.value 对于属性名称来说不是一个好主意,Adaptor 这样的名称也不是一个好类名。

    相比之下,我多次遇到过度使用双下划线名称的困难。 Python 是 Python,即使是“私有”名称也倾向于由类外部定义的代码访问。您可能认为让外部函数访问“私有”属性总是不好的做法,但是像 getattrhasattr 这样的东西呢?它们的 调用 可以在类自己的代码中,因此该类仍然控制对私有属性的所有访问,但是如果没有您手动进行名称修改,它们仍然无法工作。如果 Python 具有实际强制执行的私有变量,那么您根本无法使用它们上的那些函数。这些天来,当我编写一些非常通用的东西(如装饰器、元类或 mixin)时,我倾向于保留双下划线名称,这些东西需要向它所应用的(未知)类的实例添加“秘密属性”。

    当然还有标准的动态语言论点:现实情况是,您必须彻底测试您的代码,才能有充分的理由宣称“我的软件有效”。这样的测试不太可能错过由意外名称冲突引起的错误。如果您不进行该测试,那么 许多 更多未捕获的错误将通过意外名称冲突以外的其他方式引入。

    总而言之,在实践中,在惯用的 Python 代码中,缺少私有变量并不是什么大问题,而添加真正的私有变量会在其他方面导致更频繁的问题,恕我直言。

    【讨论】:

      【解决方案5】:

      重整发生在双下划线中。单下划线更像是“请不要”。

      您不需要知道所有父类的所有细节(请注意,通常最好避免深度继承),因为您仍然可以使用 dir() 和 help() 以及您可以提出的任何其他形式的内省.

      【讨论】:

        【解决方案6】:

        如上所述,您可以使用名称修饰。但是,如果您充分记录代码,则可以坚持使用单个下划线(或不使用下划线!) - 您不应该有太多私有变量,以至于这被证明是一个问题。只需说明一个方法是否依赖于私有变量,然后将变量或方法的名称添加到类文档字符串中以提醒用户。

        此外,如果您创建单元测试,您应该创建检查成员不变量的测试,因此这些应该能够显示此类名称冲突。

        如果您真的想拥有“私有”变量,并且无论出于何种原因名称修改无法满足您的需求,您可以将您的私有状态分解为另一个对象:

        class Foo(object):
        
             class Stateholder(object): pass
        
             def __init__(self):
                 self._state = Stateholder()
                 self.state.private = 1
        

        【讨论】:

        • 这意味着一旦你编写了一个类,你就永远不能修改它的工作方式,因为任何从它继承的人可能使用了你自己可能在修改中使用的名称?如果没有真正的方法来治疗原因,单元测试可能是治疗症状的最佳方法。
        • @Chris 你想象的问题在你想象的形式中并不存在。如果您引入一个新的公共名称,也会发生同样的事情。解决方案是配置管理,这是软件开发的正常部分。无论如何,只有在创建库时才会出现此问题 - 如果它在一个代码库中,请与您的同事交谈。
        • 在公共/私有概念非常松散的语言环境中确实如此。一种在两者之间有明显区别的语言将很容易拥有“稳定的 API,可变的内部”方法;我可以支持一个不变的 API,而不是不变的内部结构。我认为缺乏这种区别是一个问题,但我接受其他人没有。
        • @Chris 公私之间的区别似乎导致了对保护级别的许多徒劳的思考。它还促进了非常深的继承链,因为事情是“安全的”。在实践中,由于单一继承,java 最终几乎没有继承内部结构,而是依赖于接口。相比之下,python 有不止一种方法可以创建真正受保护的对象。
        猜你喜欢
        • 1970-01-01
        • 2020-10-09
        • 1970-01-01
        • 2010-11-17
        • 1970-01-01
        • 1970-01-01
        • 2022-08-18
        • 2011-12-05
        • 1970-01-01
        相关资源
        最近更新 更多