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
时会引发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
可以很容易地由第三方提供,其详细信息可能随时改变。
答案 0 :(得分:7)
使用private names(而不是受保护的),以双下划线开头:
class Sub(Base):
def __init__(self):
super(Sub, self).__init__()
self.__foo = None
# ^^
不会与_foo
中的__foo
或Base
发生冲突。这是因为Python用单个下划线和类的名称替换双下划线;以下两行是等效的:
class Sub(Base):
def x(self):
self.__foo = None # .. is the same as ..
self._Sub__foo = None
(响应编辑:)类层次结构中的两个类不仅具有相同的名称,而且它们都使用相同的属性名称,并且都使用私有的错位({{1}形式是如此微不足道,以至于在实践中可以安全地忽略它(我到目前为止还没有听说过一个案例)。
然而,理论上,你是正确的,为了正式验证程序的正确性,人们最了解整个继承链。幸运的是,正式验证通常需要一套固定的库。
这符合Zen of Python的精神,包括
实用性胜过纯洁。
答案 1 :(得分:3)
名称修改包括该类,因此您的Base.__foo
和Sub.__foo
将具有不同的名称。这就是首先在Python中添加名称修改功能的全部原因。一个是_Base__foo
,另一个是_Sub__foo
。
出于某些原因,许多人更喜欢使用合成(has-a)而不是继承(is-a)。
答案 2 :(得分:2)
这意味着我必须知道整个继承链。 。
是的,您应该知道整个继承链,或者您直接进行子类化的对象的文档应该告诉您需要知道的内容。
子类化是一项高级功能,应小心使用。
指定应该在子类中重写的内容的文档的一个很好的例子是threading class:
此类表示在单独的控制线程中运行的活动。有两种方法可以指定活动:将可调用对象传递给构造函数,或者通过覆盖子类中的
run()
方法。不应在子类中重写其他方法(构造函数除外)。换句话说,只覆盖此类的__init__()
和run()
方法。
答案 3 :(得分:2)
您多久修改一次继承链中的基类,以便从与该类的子类同名的类中引入继承????
不那么轻率,是的,你必须知道你正在使用的代码。毕竟,当然必须知道正在使用的公共名称。 Python是python,发现祖先类使用的公共名称与发现私有名称几乎完全一样。
在Python编程的多年中,我从未发现这在实践中是一个很大的问题。当您命名实例变量时,您应该非常清楚(a)名称是否足够通用以至于它可能在其他上下文中使用;(b)您正在编写的类可能涉及到与其他未知类的继承层次结构。在这种情况下,您会更仔细地考虑您正在使用的名称;对于属性名称,self.value
不是一个好主意,Adaptor
也不是一个很棒的类名。
相比之下,我多次遇到过度使用双下划线名称的困难。 Python是Python,甚至“私有”名称也往往被类外定义的代码访问。您可能认为让外部函数访问“私有”属性总是不好的做法,但getattr
和hasattr
之类的内容呢?它们的调用可以在类自己的代码中,因此该类仍然控制对私有属性的所有访问,但是如果没有手动进行名称修改,它们仍然无法工作。如果Python实际上强制执行私有变量,则根本不能使用类似于它们的函数。这些天我倾向于保留双下划线名称,因为我正在写一些非常通用的东西,如装饰器,元类或mixin,需要为它应用的(未知)类的实例添加“秘密属性”。
当然还有标准的动态语言论证:现实是你必须彻底测试你的代码,以便在声明“我的软件正常工作”时有充分的理由。这样的测试不太可能错过因意外冲突而造成的错误。如果您没有进行该测试,那么许多更多未被捕获的错误将通过其他方式引入,而不是偶然的名称冲突。
总而言之,缺乏私有变量在实践中并不是惯用的Python代码中的大问题,并且添加真正的私有变量会在其他方面导致更频繁的问题恕我直言。
答案 4 :(得分:0)
如上所述,您可以使用名称修改。但是,如果你充分记录你的代码,你可以坚持使用单个下划线(或者没有!) - 你不应该有这么多私有变量,这证明是一个问题。只是说一个方法依赖于一个私有变量,并将该变量或方法的名称添加到类docstring中以提醒用户。
此外,如果您创建单元测试,则应创建检查成员不变量的测试,因此这些应该能够显示此类名称冲突。
如果你真的想拥有“私人”变量,并且无论出于什么原因,名称变形不能满足你的需求,你可以将私有状态分解为另一个对象:
class Foo(object):
class Stateholder(object): pass
def __init__(self):
self._state = Stateholder()
self.state.private = 1
答案 5 :(得分:0)
Manchling发生双下划线。单下划线更像是“请不要”。
您不需要知道所有父类的所有细节(请注意,通常最好避免使用深度继承),因为您仍然可以提出dir()和help()以及任何其他形式的内省