Pau*_*nta 89 python naming-conventions
在其他语言中,有助于生成更好代码的一般准则总是尽可能隐藏所有内容.如果对变量是私有还是受保护存在疑问,最好与私有变量一起使用.
对Python来说也是如此吗?我应该首先在所有内容上使用两个前导下划线,并且只在我需要时将它们隐藏起来(只有一个下划线)吗?
如果惯例只使用一个下划线,我也想知道其基本原理.
这是我在JBernardo的回答中留下的评论.它解释了为什么我问这个问题,以及为什么我想知道为什么Python与其他语言不同:
我来自语言,训练你认为一切都应该只是公开的,而不是更多.原因是这将减少依赖性并使代码更安全.反向做事的Python方式 - 从公共开始到隐藏 - 对我来说很奇怪.
bra*_*zzi 168
如有疑问,请将其保留为"公开" - 我的意思是,不要添加任何内容来模糊属性的名称.如果你有一个内部价值的类,不要打扰它.而不是写:
class Stack(object):
def __init__(self):
self.__storage = [] # Too uptight
def push(self, value):
self.__storage.append(value)
Run Code Online (Sandbox Code Playgroud)
默认情况下写这个:
class Stack(object):
def __init__(self):
self.storage = [] # No mangling
def push(self, value):
self.storage.append(value)
Run Code Online (Sandbox Code Playgroud)
这肯定是一种有争议的做事方式.Python新手只是讨厌它,甚至一些老Python家伙都鄙视这个默认值 - 但无论如何它都是默认值,所以我真的建议你遵循它,即使你觉得不舒服.
如果您真的想将消息发送给用户说"无法触摸此内容!",通常的方法是在变量前加一个下划线.这只是一个惯例,但人们理解它并在处理这些东西时要小心谨慎:
class Stack(object):
def __init__(self):
self._storage = [] # This is ok but pythonistas use it to be relaxed about it
def push(self, value):
self._storage.append(value)
Run Code Online (Sandbox Code Playgroud)
这对于避免属性名称和属性名称之间的冲突也很有用:
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
Run Code Online (Sandbox Code Playgroud)
双下划线怎么样?好吧,双下划线魔术主要用于避免意外重载方法和名称与超类属性冲突.如果你编写一个预期会被扩展很多次的类,它会非常有用.
如果你想将它用于其他目的,你可以,但它既不常见也不推荐.
编辑:为什么会这样?嗯,通常的Python风格并不强调私有化 - 恰恰相反!这有很多原因 - 大多数都是有争议的...让我们看看其中的一些.
今天大多数OO语言使用相反的方法:不应该使用的不应该是可见的,因此属性应该是私有的.从理论上讲,这将产生更易于管理,更少耦合的类,因为没有人会不顾一切地改变对象内部的值.
但是,这并不是那么简单.例如,Java类确实有很多属性和 getter只能获取只设置值的值和 setter .我们需要说七行代码来声明一个属性 - 一个Python程序员会说这是不必要的复杂.此外,在实践中,您只需编写大量代码即可获得一个公共字段,因为您可以使用getter和setter更改其值.
那么为什么要遵循这种私有的默认政策呢?默认情况下,只需将您的属性公开.当然,它在Java中存在问题,因为如果您决定为属性添加一些验证,则需要您更改所有属性
person.age = age;
Run Code Online (Sandbox Code Playgroud)
在我们的代码中,让我们说,
person.setAge(age);
Run Code Online (Sandbox Code Playgroud)
是setAge():
public void setAge(int age) {
if (age >= 0) {
this.age = age;
} else {
this.age = 0;
}
}
Run Code Online (Sandbox Code Playgroud)
因此,在Java(和其他语言)中,默认情况下使用getter和setter,因为它们可能会令人烦恼,但是如果你发现自己处于我所描述的情况,可以节省很多时间.
但是,您不需要在Python中执行此操作,因为Python具有属性.如果你有这个课程:
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
Run Code Online (Sandbox Code Playgroud)
然后你决定验证年龄,你不需要改变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
Run Code Online (Sandbox Code Playgroud)
如果你能做到并仍然使用 person.age = age,为什么要添加私有字段和getter和setter?
(另外,请参阅Python不是Java和本文关于使用getter和setter的危害.).
即使在有私有属性的语言中,您也可以通过某种反射/内省库来访问它们.人们在框架和解决紧急需求方面做了很多次.问题在于内省库只是一种很难用公共属性做的事情.
由于Python是一种非常动态的语言,因此将这种负担添加到您的类中会适得其反.
对于Pythonista来说,封装不是无法查看类的内部,而是避免查看它的可能性.我的意思是,封装是组件的属性,允许在没有用户关注内部细节的情况下使用它.如果您可以使用组件而不必担心其实现,那么它将被封装(在Python程序员看来).
现在,如果你以这种方式编写你的类,你可以使用它而不必考虑实现细节,如果你想因为某些原因查看类内部没有问题.问题是:您的API应该是好的,其余的是详细的.
嗯,这没有争议:他实际上是这么说的.(寻找"开放式和服.")
是的,有一些原因,但没有杀人原因.这主要是Python编程的文化方面.坦率地说,它也可能是另一种方式 - 但事实并非如此.另外,您可以反过来问:为什么某些语言默认使用私有属性?对于Python实践的相同主要原因:因为它是这些语言的文化,并且每种选择都有优点和缺点.
自从这种文化成长以来,建议您遵循它.否则,__当你在Stack Overflow中问一个问题时,你会对Python程序员说你要删除你的代码感到厌烦:)
Aar*_*all 16
当您在类定义中并且使用__any_name或者__any_name_,即两个(或更多)前导下划线和最多一个尾随下划线时,将调用名称修改.
class Demo:
__any_name = "__any_name"
__any_other_name_ = "__any_other_name_"
Run Code Online (Sandbox Code Playgroud)
现在:
>>> [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_'
Run Code Online (Sandbox Code Playgroud)
如果有疑问,做什么?
表面上的用法是防止子类使用该类使用的属性.
潜在的价值在于避免与想要覆盖行为的子类的名称冲突,以便父类功能保持按预期工作.但是,Python文档中的示例不是Liskov可替代的,并且没有任何示例可以解释我发现它有用的地方.
缺点是它增加了阅读和理解代码库的认知负荷,特别是在调试时你看到源中的双下划线名称和调试器中的错位名称.
我个人的方法是故意避免它.我在一个非常大的代码库上工作.它的罕见用途像拇指疼痛一样突出,似乎不合理.
你确实需要注意它,所以当你看到它时你就会知道它.
PEP 8,Python标准库样式指南,目前说(删节):
有一些关于使用的争议
__names.如果您的类要进行子类化,并且您具有不希望使用子类的属性,请考虑使用双前导下划线和没有尾随下划线来命名它们.
请注意,在修改的名称中只使用简单的类名,因此如果子类同时选择相同的类名和属性名,则仍然可以获得名称冲突.
名称修改可以进行某些用途,例如调试
__getattr__(),不太方便.但是,名称修改算法已有详细记录,并且易于手动执行.不是每个人都喜欢名字错误.尽量平衡避免意外姓名冲突与高级呼叫者潜在使用的需要.
如果在类定义中添加两个下划线(不带双重下划线),则名称将被修改,并且类名称后面的下划线将被添加到对象上:
>>> class Foo(object):
... __foobar = None
... _foobaz = None
... __fooquux__ = None
...
>>> [name for name in dir(Foo) if 'foo' in name]
['_Foo__foobar', '__fooquux__', '_foobaz']
Run Code Online (Sandbox Code Playgroud)
请注意,只有在解析类定义时,名称才会被破坏:
>>> 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'
Run Code Online (Sandbox Code Playgroud)
此外,那些刚接触Python的人有时无法理解当他们无法手动访问他们在类定义中定义的名称时会发生什么.这不是反对它的强有力理由,但如果您有学习受众,则需要考虑这一点.
如果惯例只使用一个下划线,我也想知道其基本原理.
当我的目的是让用户放弃属性时,我倾向于只使用一个下划线,但这是因为在我的心理模型中,子类可以访问名称(他们总是有,因为他们可以很容易地发现无论如何都是错误的名字).
如果我正在审查使用__前缀的代码,我会问他们为什么要调用名称修改,如果他们不能用单个下划线做同样的事情,请记住,如果子类为这个类选择相同的名称,尽管如此,class属性仍然存在名称冲突.
Mat*_*ner 15
我不会说这种做法会产生更好的代码.可见性修饰符只会分散您对手头任务的注意力,并且作为副作用会强制您的界面按预期使用.一般来说,强制可见性可以防止程序员在没有正确阅读文档的情况下弄乱.
一个更好的解决方案是Python鼓励的路径:您的类和变量应该有很好的文档记录,并且它们的行为是明确的.来源应该可用.这是编写代码的更具可扩展性和可靠性的方法.
我在Python中的策略是这样的:
最重要的是,应该清楚一切都在做什么.如果其他人将使用它,请记录下来.如果您希望它在一年的时间内有用,请记录下来.
作为旁注,您实际上应该使用其他语言中的受保护:您永远不会知道您的类可能会在以后继承以及它可能被使用.最好只保护那些你确定不能或不应该被外国代码使用的变量.
您不应该从私有数据开始,并在必要时将其公开.相反,您应该首先弄清楚对象的界面.也就是说,你应该首先弄清楚世界看到了什么(公共事物),然后找出必要的私人物品.
其他语言很难让曾经公开的私人语言.即如果我将变量设为私有或受保护,我将破坏大量代码.但是在python中的属性并非如此.相反,即使重新安排内部数据,我也可以保持相同的界面.
_和__之间的区别在于python实际上试图强制执行后者.当然,它并没有真正努力,但确实让它变得困难._只是告诉其他程序员意图是什么,他们可以自由地忽视他们的危险.但忽略这条规则有时会有所帮助.示例包括调试,临时黑客攻击以及使用不打算以您使用方式使用的第三方代码.
已经有很多很好的答案,但我会提供另一个.这也部分是对那些一直说双下划线不是私人(实际上是)的人的回应.
如果你看一下Java/C#,它们都有private/protected/public.所有这些都是编译时构造.它们仅在编译时强制执行.如果您在Java/C#中使用反射,则可以轻松访问私有方法.
现在,每次在Python中调用函数时,您都固有地使用反射.这些代码段在Python中是相同的.
lst = []
lst.append(1)
getattr(lst, 'append')(1)
Run Code Online (Sandbox Code Playgroud)
"dot"语法只是后一段代码的语法糖.主要是因为使用getattr已经很难看,只有一个函数调用.它从那里变得更糟.
因此,没有 Java/C#版本的私有,因为Python不编译代码.Java和C#无法在运行时检查函数是私有的还是公共的,因为该信息已经消失(并且它不知道函数的调用位置).
现在有了这些信息,双下划线的名称变形对于实现"私密性"最有意义.现在当从'self'实例调用一个函数并且它注意到它以'__'开头时,它只是在那里执行名称修改.这只是更多的语法糖.该语法糖允许在仅使用反射进行数据成员访问的语言中等效于"私有".
免责声明:我从未听过Python开发人员说过这样的话.缺乏"私人"的真正原因是文化,但你也会注意到大多数脚本/解释语言都没有私有.除编译时间外,严格可执行的私有对任何事情都不实用.
第一:为什么要隐藏你的数据?为什么这如此重要?
大多数时候你并不是真的想这样做,但你这样做是因为其他人正在这样做。
如果你真的真的不想让人们使用某些东西,请在它前面添加一个下划线。就是这样……Pythonistas 知道带有一个下划线的东西并不能保证每次都能工作,并且可能会在你不知情的情况下发生变化。
这就是我们的生活方式,我们对此表示同意。
使用两个下划线会让你的类很难子类化,甚至你也不想这样工作。
所选择的答案很好地解释了属性如何消除对私有属性的需要,但我还会在模块级别添加该函数,从而消除对私有方法的需要。
如果在模块级别将方法转换为函数,则子类将无法覆盖它。将某些功能移动到模块级别比尝试使用名称修改隐藏方法更 Pythonic。
小智 5
以下代码片段将解释所有不同的情况:
没有下划线 (a)
class Test:
def __init__(self):
self.__a = 'test1'
self._a = 'test2'
self.a = 'test3'
def change_value(self,value):
self.__a = value
return self.__a
Run Code Online (Sandbox Code Playgroud)打印测试对象的所有有效属性
testObj1 = Test()
valid_attributes = dir(testObj1)
print valid_attributes
['_Test__a', '__doc__', '__init__', '__module__', '_a', 'a',
'change_value']
Run Code Online (Sandbox Code Playgroud)
在这里,您可以看到 __a 的名称已更改为 _Test__a 以防止此变量被任何子类覆盖。这个概念在 python 中被称为“Name Mangling”。你可以这样访问:
testObj2 = Test()
print testObj2._Test__a
test1
Run Code Online (Sandbox Code Playgroud)
同样,在_a的情况下,该变量只是通知开发人员它应该用作该类的内部变量,即使您访问它,python解释器也不会做任何事情,但这不是一个好习惯。
testObj3 = Test()
print testObj3._a
test2
Run Code Online (Sandbox Code Playgroud)
变量可以从任何地方访问,就像公共类变量一样。
testObj4 = Test()
print testObj4.a
test3
Run Code Online (Sandbox Code Playgroud)
希望回答对你有帮助:)