7

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()

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

这意味着我必须知道整个继承链,包括每个使用的所有“私有”对象。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可以很容易地由第三方提供,其细节可能随时更改。

4

6 回答 6

7

使用私有名称(而不是受保护的名称),以双下划线开头:

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

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

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

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

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

这是Python之禅的精神,其中包括

实用胜过纯洁。

于 2012-07-24T21:41:04.497 回答
3
  1. 名称修饰包括该类,因此您的Base.__fooSub.__foo将具有不同的名称。这就是首先将名称修饰功能添加到 Python 的全部原因。一个将是_Base__foo,另一个_Sub__foo

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

于 2012-07-24T21:41:58.520 回答
2

这意味着我必须知道整个继承链。. .

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

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

指定子类中应该覆盖什么的文档的一个很好的例子是线程类

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

于 2012-07-24T22:16:55.787 回答
2

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

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

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

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

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

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

于 2012-07-25T02:57:25.243 回答
0

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

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

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

class Foo(object):

     class Stateholder(object): pass

     def __init__(self):
         self._state = Stateholder()
         self.state.private = 1
于 2012-07-24T21:48:03.360 回答
0

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

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

于 2012-07-24T21:50:50.217 回答