2012-07-24 27 views
7

Python类没有public/private的概念,所以我们被告知不要触及以下划线开头的东西,除非我们创建它。但是这并不需要我们直接或间接继承的所有类的完整知识吗?证人: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的类(在单独的包中)会发生什么?现在__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可以很容易地由第三方提供,其细节可以在任何时候改变。

+0

你可以发布一个例子来演示这个地方其他同名的类不会完全影响你现有的类吗..或者我还是不明白..nevermind ...我做了一个和你的权利我猜这是一个角落case ...只是尽量不要使用常见的私人var /类名称我猜 – 2012-07-24 22:04:01

回答

2

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

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

子类化是一项高级功能,应小心处理。

文档指定什么应该在子类中被覆盖的一个很好的例子是threading class

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

7

使用private names(而不是保护的),开始以双下划线:

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的精神,其包括

实用性节拍纯度。

+0

但它会与假设的'Base .__ foo'发生冲突吗? – inspectorG4dget 2012-07-24 21:41:48

+0

@ inspectorG4dget不是。 – phihag 2012-07-24 21:42:32

+0

我认为这是值得在你的答案特别提到,因为它更符合什么OP似乎要求 – inspectorG4dget 2012-07-24 21:43:42

3
  1. 名称重整包括类,所以你Base.__fooSub.__foo会有不同的名字。这是首先将名称加密功能添加到Python的全部原因。一个是_Base__foo,另一个是_Sub__foo

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

+0

是不是这个错误?我认为只有发生双重下划线不能单独发生... – 2012-07-24 21:46:32

+0

我提到名字mangling;我注意到的特殊情况是A-> B-> A(其中第一个A显然是在一个单独的包中)。现在,祖父类会像我的子类一样破坏事物,消除名称集合提供的优势。 – Chris 2012-07-24 21:47:35

+0

@JoranBeasley:我使用双下划线... – 2012-07-24 23:21:24

0

如上所述,您可以使用名称修饰。然而,如果你充分记录你的代码,你可以坚持一个下划线(或者没有!) - 你不应该有太多的私有变量,这证明是一个问题。只要说一个方法依赖于一个私有变量,并将该变量或方法的名称添加到类文档字符串中以提醒用户。另外,如果你创建单元测试,你应该创建测试来检查成员的不变量,因此这些应该能够显示出这样的名称冲突。

如果你真的想拥有“私有”变量,不管出于什么原因名称忙玲不能满足你的需求,那么您可以您的私人状态到另一个对象:

class Foo(object): 

    class Stateholder(object): pass 

    def __init__(self): 
     self._state = Stateholder() 
     self.state.private = 1 
+0

这意味着,一旦你写了一个类,你永远不能修改它的工作方式,因为任何从它继承的人可能使用了你自己可能会用在你的名字修改?如果没有真正的治疗方法,单元测试可能是治疗症状的最佳方法。 – Chris 2012-07-24 22:15:35

+0

@Chris你想象中的问题并不存在于你想象的形式中。如果你引入一个新的公共名称,也会发生同样的事情。解决方案是配置管理,这是软件开发的一个正常部分。在任何情况下,只有在创建库时才会出现此问题 - 如果它位于一个代码库中,只需向同事说明即可。 – Marcin 2012-07-24 22:26:52

+0

在公共/私人理念非常松散的语言中,情况确实如此。两者之间具有强烈区别的语言将使得拥有“稳定的API,可变内部”方法变得容易;我可以获得一个不变的API,而不仅仅是不变的内部。我认为缺乏这样的区分是一个问题,但我接受其他人不这样做。 – Chris 2012-07-24 22:44:08

0

忙玲发生双下划线。单下划线更多的是“请不要”。你不需要知道所有父类的所有细节(注意深度继承通常是最好的避免),因为你仍然可以使用dir()和help()以及任何其他形式的自省用。

2

您多久修改一次继承链中的基类,以引入继承链中的继承?

不那么轻浮,是的,你必须知道你正在使用的代码。毕竟,你必须知道所使用的公共名称。 Python是python,发现祖先类使用的公有名称与发现私有名称几乎一样努力。

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

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

当然还有标准的动态语言参数:事实是,您必须彻底测试您的代码,才能在声明“我的软件能够正常工作”时有足够的理由。这样的测试将不太可能错过由意外冲突引起的错误。如果你没有进行这种测试,那么很多更多的未被发现的错误将通过其他方式引入,而不是意外的名称冲突。

总而言之,实践中缺乏私有变量在惯用Python代码中并不算什么大事,而增加真正的私有变量会导致其他方式更频繁的问题。