Python的名字不断变化
在其他语言中,有助于生成更好的代码的一般指导原则总是尽可能隐藏所有内容。 如果不确定某个variables是否应该是私人的或者是受保护的,那么最好是与私人相关。
这对Python是否也适用? 我是否应该首先在所有内容上使用两个主要的下划线,并且只在需要时将它们隐藏起来(只有一个下划线)?
如果约定只使用一个下划线,我也想知道理由。
这是我留在JBernardo答案的评论。 它解释了为什么我问这个问题,为什么我想知道为什么Python与其他语言不同:
我来自培养你的语言,认为所有的东西都应该像所需要的一样公开。 理由是这将减less依赖性并使代码更安全地进行更改。 Python的做法是相反的 – 从公开开始走向隐藏 – 对我来说是很奇怪的。
如有疑问,请将其保留为“公开” – 我的意思是,不要添加任何内容来遮盖您的属性名称。 如果你有一个具有内在价值的课程,不要为此烦恼。 而不是写作:
class Stack(object): def __init__(self): self.__storage = [] # Too uptight def push(self, value): self.__storage.append(value)
默认写这个:
class Stack(object): def __init__(self): self.storage = [] # No mangling def push(self, value): self.storage.append(value)
这肯定是一个有争议的做事方式。 Python的新手只是讨厌它,甚至一些老的Python家伙鄙视这个默认 – 但它是默认情况下,所以我真的build议你遵循它,即使你感到不舒服。
如果你真的想发送消息给你的用户说“不能碰这个!”,通常的方法是在variables前面加一个下划线。 这只是一个惯例,但是人们在处理这样的事情时会理解它,
class Stack(object): def __init__(self): self._storage = [] # This is ok but pythonistas use to be relaxed about it def push(self, value): self._storage.append(value)
这也可以用来避免属性名称和属性名称之间的冲突:
class Person(object): def __init__(self, name, age): self.name = name self._age = age if age >= 0 else 0 @property def age(self): return self._age @age.setter def age(self, age): if age >= 0: self._age = age else: self._age = 0
双下划线呢? 那么,双下划线魔术主要用来避免方法的意外重载和名称与超类属性的冲突 。 如果你写了一个预计会被多次扩展的类,这将是非常有用的。
如果你想用于其他目的,你可以,但它是既不平常也不build议。
编辑 :这是为什么呢? 那么,通常的Python风格并不强调将事物私人化 – 相反! 这有很多原因 – 其中大多数是有争议的…让我们看看其中的一些。
Python有属性
现在大多数面向对象的语言使用相反的方法:不应该使用不应该被使用的东西,所以属性应该是私有的。 从理论上讲,这将产生更多的可pipe理,更less耦合的类,因为没有人会鲁莽地改变对象内的值。
但是,这并不是那么简单。 例如,Java类确实有很多属性和 getter,它们只是获取设置值的值和设置器。 你需要,让我们说,七行代码来声明一个单一的属性 – 一个Python程序员会说是不必要的复杂。 另外,在实践中,你只需要编写大量的代码来获得一个公共字段,因为你可以使用getter和setter来改变它的值。
那么为什么要遵循这个私密的默认策略呢? 只需要将你的属性默认为公共的。 当然,这在Java中是有问题的,因为如果你决定给你的属性添加一些validation,就需要你全部改变
person.age = age;
在你的代码中,让我们说,
person.setAge(age);
正在setAge()
:
public void setAge(int age) { if (age >= 0) { this.age = age; } else { this.age = 0; } }
所以在Java(和其他语言)中,默认情况下是使用getter和setter,因为它们可能会令人讨厌写入,但是如果发现自己处于所描述的状态,可以省去很多时间。
但是,你不需要用Python来完成,因为Python有属性。 如果你有这个class级:
class Person(object): def __init__(self, name, age): self.name = name self.age = age
然后你决定validation年龄,你不需要改变person.age = age
你的代码片段。 只需添加一个属性(如下所示)
class Person(object): def __init__(self, name, age): self.name = name self._age = age if age >= 0 else 0 @property def age(self): return self._age @age.setter def age(self, age): if age >= 0: self._age = age else: self._age = 0
如果你能做到这一点,仍然使用person.age = age
,为什么你会添加私人领域和getter和setters?
(另外,请参阅Python不是Java和本文中关于使用getter和setter的危害 )。
无论如何,任何事情都是可见的 – 试图隐藏只会使你的工作复杂化
即使在有私有属性的语言中,也可以通过某种reflection/自省库来访问它们。 人们通过框架和解决紧迫的需求来做很多事情。 问题是内省库只是一个很难做的事,你可以用公有属性做什么。
由于Python是一种非常dynamic的语言,因此将这种负担添加到类中只是适得其反。
问题是不可能看到的 – 是需要看到的
对于一个Pythonista来说,封装并不是看不到类内部的东西,而是避免去看它的可能性。 我的意思是,封装是一个组件的属性,它允许在用户不关心内部细节的情况下使用它。 如果你可以使用一个组件而不打扰自己的实现,那么它就被封装了(在Python程序员看来)。
现在,如果你以这样的方式编写你的类,你可以使用它,而不必考虑实现的细节,如果你想看看类内部出于某种原因没有问题。 问题是:你的API应该是好的,其余的细节。
圭多这样说
那么这是没有争议的, 他其实是这样说的 。 (找“打开和服”。)
这是文化
是的,有一些原因,但没有杀人的原因。 这主要是Python编程的一个文化方面。 坦率地说,也可能是另一种方式 – 但事实并非如此。 另外,你也可以反过来问:为什么某些语言默认使用私有属性? Python实践的主要原因是:因为它是这些语言的文化,每个select都有优点和缺点。
自从这种文化长大以后,你最好build议遵循。 否则,当你在Stack Overflow中提出一个问题时,你会被Python程序员说让你去除你的代码的__
的烦恼:)
我不会说实践会产生更好的代码。 可见性修饰符只会分散您的工作,并且作为副作用强制您的界面按照您的意图使用。 一般来说,如果程序员没有正确地阅读文档,那么强制执行可见性就可以防止程序员搞砸了。
一个更好的解决scheme是Python鼓励的路线:你的类和variables应该被很好地logging下来,并且他们的行为是清楚的。 源应该可用。 这是编写代码更可扩展和可靠的方法。
我的Python战略是这样的:
- 只要写下该死的东西,不要假设你的数据应该如何保护。 这假定你写的是为你的问题创build理想的接口。
- 使用前导下划线表示可能不会在外部使用的东西,而不是正常的“客户端代码”界面的一部分。
- 只使用双下划线表示课堂内纯粹便利的事物,否则会在意外暴露的情况下造成相当大的伤害。
最重要的是,应该清楚一切事情。 如果其他人将使用它,请将其logging下来。 如果你希望它在一年内有用,请logging下来。
作为一个方面说明,你实际上应该用其他语言进行保护 :你永远不会知道你的类可能会在以后被inheritance,也可能会被使用。 最好只保护那些你确定的variables不能或不应该被外国代码使用。
您不应该从私人数据开始,并根据需要进行公开。 相反,你应该开始弄清楚你的对象的接口。 也就是说,你应该首先弄清楚世界所看到的(公众的东西),然后弄清楚什么是私人的东西是必要的。
其他语言使得曾经公开的私人事业变得困难。 即我将打破许多代码,如果我让我的variables私人或保护。 但是在python中的属性不是这种情况。 相反,即使重新安排内部数据,我也可以保持相同的界面。
_和__之间的区别在于python实际上试图强制后者。 当然,这并不是真的很难,但确实很难。 _只是告诉其他程序员他们的意图是什么,他们可以自由地忽略他们的危险。 但是忽略这个规则有时候是有帮助的。 示例包括debugging,临时黑客和使用第三方代码,这些代码不打算以您使用它的方式使用。
已经有很多很好的答案了,但是我会再提供一个。 这也是对那些一直说双倍下划线不是私人的人的回应(它确实是这样)。
如果你看Java / C#,他们都有private / protected / public。 所有这些都是编译时构造 。 它们只在编译时执行。 如果您要在Java / C#中使用reflection,则可以轻松访问私有方法。
现在,每当你用Python调用一个函数时,你都在使用reflection。 这些代码在Python中是一样的。
lst = [] lst.append(1) getattr(lst, 'append')(1)
“点”语法只是后面那段代码的语法糖。 主要是因为只有一个函数调用,使用getattr已经很难看了。 它从那里变得更糟。
因此,由于Python不能编译代码,所以不能有Java / C#版本的私有版本。 Java和C#不能检查一个函数在运行时是私有的还是公共的,因为这些信息已经消失了(并且它不知道函数被调用的地方)。
现在有了这些信息,双下划线的名字就成了实现“私密性”的最有意义的事情。 现在,当从“self”实例调用一个函数并注意到它以“__”开头时,它只是在那里执行名称修改。 这只是更多的语法糖。 这个语法糖在一种只使用reflection来访问数据成员的语言中允许相当于“私有”。
免责声明:我从来没有听说任何来自Python开发人员说这样的事情。 缺乏“私人”的真正原因是文化,但你也会注意到,大多数脚本/解释语言没有私人的。 除了编译时间以外,严格可执行的私有程序是不实用的。
首先 – 什么是名字?
在类定义中调用名称调用,并使用__any_name
或__any_name_
,即两个 (或多个)前导下划线和最多一个尾随下划线。
class Demo: __any_name = "__any_name" __any_other_name_ = "__any_other_name_"
现在:
>>> [n for n in dir(Demo) if 'any' in n] ['_Demo__any_name', '_Demo__any_other_name_'] >>> Demo._Demo__any_name '__any_name' >>> Demo._Demo__any_other_name_ '__any_other_name_'
如果有疑问,做什么?
表面上的用法是防止子类使用该类使用的属性。
潜在的价值在于避免与想要重写行为的子子程序的名称冲突,以便父类function按预期保持正常工作。 然而,Python文档中的例子不是Liskov可以替代的,没有任何例子可以让我想到这个有用的地方。
缺点是它增加了阅读和理解代码库的认知负载,特别是在debugging时,在源代码中看到双下划线名称和在debugging器中出现错位的名称时尤其如此。
我个人的做法是有意避免它。 我在一个非常大的代码基础上工作。 它的罕见用途就像一个拇指拇指伸出,似乎并不合理。
你需要知道它,所以当你看到它时就知道它。
PEP 8
PEP 8 ,Python标准库风格指南,目前说(删节):
关于使用
__names
有一些争议。如果你的类想要被子类化,并且你有不希望子类使用的属性,考虑用双引号强调下划线并且不用尾随下划线。
请注意,只有简单的类名称在mangled名称中使用,所以如果子类select相同的类名称和属性名称,仍然可以获得名称冲突。
名称的修改可以使某些用途,如debugging和
__getattr__()
,不太方便。 然而,名字修改algorithm是有据可查的,并且易于手动执行。不是每个人都喜欢名字捣毁。 尝试平衡需要避免意外的名称冲突与高级来电者的潜在使用。
它是如何工作的?
如果在类定义中预先加两个下划线(不结束双下划线),则名称将被破坏,并且下一个下划线后面跟着类名称将被添加到该对象上:
>>> class Foo(object): ... __foobar = None ... _foobaz = None ... __fooquux__ = None ... >>> [name for name in dir(Foo) if 'foo' in name] ['_Foo__foobar', '__fooquux__', '_foobaz']
请注意,只有在parsing类定义时,名称才会变形:
>>> Foo.__test = None >>> Foo.__test >>> Foo._Foo__test Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: type object 'Foo' has no attribute '_Foo__test'
而且,对于Python新手来说,当他们无法手动访问他们在类定义中定义的名称时,他们有时会无法理解正在发生的事情。 这不是一个强烈的反对它的理由,但是如果你有一个学习的受众,这是一个需要考虑的问题。
一个下划线?
如果约定只使用一个下划线,我也想知道理由。
当我的意图是让用户把他们的手从一个属性,我倾向于只使用一个下划线,但是这是因为在我的心智模式,子类将有机会获得名称(他们总是有,因为他们可以很容易地发现不pipe怎么说,这个名字是错的。
如果我正在检查使用__
前缀的代码,我会问他们为什么要调用名称调整,如果他们不能使用单个下划线,请记住,如果子调用程序为类select相同的名称尽pipe如此,类属性将会有名称冲突。
首先:你为什么要隐藏你的数据? 为什么这么重要?
大多数时候你并不是真的想这样做,而是因为别人在做。
如果你真的真的不想让别人使用某些东西, 那么在它前面添加一个下划线。 就是这样… Pythonistas知道一个下划线的东西不能保证每次都有效,而且可能在你不知情的情况下改变。
这就是我们生活的方式,我们也没关系。
使用两个下划线会使你的类变得不好inheritance,即使你不想这样工作。
所选的答案在解释属性如何去除对私有属性的需求方面做得很好,但是我还会在模块级别添加这些function,而不再需要私有方法 。
如果您在模块级别将一个方法转换为函数,则可以删除子类的机会来覆盖它。 将一些function移动到模块级别比试图隐藏具有名称改变的方法更加Pythonic。
乍一看,它应该是相同的其他语言(在“其他”我的意思是Java或C + +),但事实并非如此。
在Java中,您创build了私有的所有variables,不应该在外部访问。 在Python的同一时间,你不能做到这一点,因为没有“私密性”(正如Python原则之一 – “我们都是成年人”)。 所以双下划线只表示“伙计们,不要直接使用这个字段”。 同样的含义有单独的下划线,当你不得不从被考虑的类inheritance时(这只是双下划线引起的一个可能的问题的例子),同时不会引起任何头痛。
所以,我build议你使用默认的单个下划线“私人”的成员。