在Python中,对象名称之前的单下划线和双下划线分别代表什么意思?
在Python中,对象名称之前的单下划线和双下划线分别代表什么意思?
在一个类中,以单下划线作为前缀的名称表明属性或方法仅供该类内部使用,但并不会以任何方式强制使其私有化。 在模块中,将函数名前加上单下划线表示该函数不应从其他地方导入。
来自PEP-8风格指南:
_single_leading_underscore
: 表示弱的“内部使用”指示符。例如,from M import *
不会导入以单下划线开头的对象。
根据Python文档:
任何形如
__spam
的标识符(至少两个前导下划线,最多一个尾随下划线)都会被文本替换为_classname__spam
,其中classname
是当前类名,前导下划线被剥离。这种改写是不考虑标识符的语法位置的,因此它可用于定义类专属的实例变量和类变量、方法、存储在全局变量中的变量,甚至是存储在其他类实例上私有的变量。
同一页文档中的警告:
名称改写旨在为类提供一种简单的方法来定义“私有”实例变量和方法,而无需担心派生类定义了实例变量或由类外部代码修改实例变量。请注意,改写规则主要是为了避免意外情况;尽管如此,仍然有可能让有决心的人访问或修改被视为私有的变量。
>>> class MyClass():
... def __init__(self):
... self.__superprivate = "Hello"
... self._semiprivate = ", world!"
...
>>> mc = MyClass()
>>> print mc.__superprivate
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: myClass instance has no attribute '__superprivate'
>>> print mc._semiprivate
, world!
>>> print mc.__dict__
{'_MyClass__superprivate': 'Hello', '_semiprivate': ', world!'}
from M import *
处理方式不同...因此确实进行了某些特殊处理... - flow2k_foo
: 这只是一种约定。程序员用这种方式来表明变量是私有的(在Python中意味着什么,由程序员自己定义)。
__foo
: 这具有实际意义。解释器将此名称替换为_classname__foo
,以确保该名称不会与另一个类中的类似名称重叠。
__foo__
: 这也只是一种约定。 Python系统使用这种方式避免名称与用户名称冲突。
在Python世界中,没有其他形式的下划线具有特殊含义。 此外,在这些约定中,类、变量、全局等没有区别。
__foo
,很好奇。它如何与其他类中的类似方法名称重叠?我的意思是,你仍然需要像访问 instance.__foo()
一样访问它(如果解释器没有将其重命名),对吗? - Bibhas Debnathfrom module import *
不会导入以下划线开头的对象。因此, _foo
不仅是一种约定。 - dotancohenB
继承自类A
,并且两者都实现了foo()
方法,则B.foo()
会覆盖从A
继承的.foo()
方法。B
类的实例只能访问B.foo()
方法,除非通过 super(B).foo()
方法。 - naught101from foobar import *
,并且模块foobar
没有定义一个__all__
列表,则从该模块导入的名称不包括下划线开头的名称。可以说这主要是一种惯例,因为这种情况是一个相当晦涩的角落;-)。raise NotImplementedError
!)通常是单个下划线的名称,以向使用该类(或子类)实例的代码表明,这些方法不应直接被调用。_get
和_put
等方法;“客户端代码”永远不会调用那些(“挂钩”)方法,而是调用(“组织”)公共方法,如put
和get
(这被称为模板方法设计模式——例如,可以在此处找到一个基于我对该主题的演讲视频的有趣演示,其中包括摘要)。_var_name
还是使用 var_name
并将其从 __all__
中排除? - endolith__all__
。因此,大多数情况下,答案是“两者都需要”。 - abarnert_
为private时,我不喜欢它。显然,我在谈论比喻,因为在Python中没有真正的private。当深入语义时,我会说我们可以将_
与Java的protected联系起来,因为在Java中,protected表示“派生类和/或同一包内”。将包替换为模块,因为PEP8已经告诉我们,在讨论*
导入时,_
不仅仅是一个约定,就这样。当谈到类内标识符时,__
肯定相当于Java的private。 - Marius Mucenicu._variable
是半私有的,只是为了约定而存在
.__variable
经常被错误地认为是超级私有的,它实际上的意义只是为了进行名称混淆,以防止意外访问[1]
.__variable__
通常保留给内置的方法或变量
如果你非常想要访问 .__mangled
变量,你仍然可以这样做。双下划线只是对变量进行名称混淆,或者重命名为像instance._className__mangled
这样的东西
例子:
class Test(object):
def __init__(self):
self.__a = 'a'
self._b = 'b'
>>> t = Test()
>>> t._b
'b'
t._b是可访问的,因为它只是按照惯例隐藏起来的。
>>> t.__a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute '__a'
t.__a没有找到,是因为由于名称重整它已经不存在了
>>> t._Test__a
'a'
通过访问instance._className__variable
而不是仅仅使用双下划线名称,您可以访问隐藏的值。
._variable
不仅是一种约定:"from M import *
" 不会导入以下划线开头的对象。然而,在所呈现的情况下,将其显示为类属性并不会改变任何内容。 - pdaawr在方法名或属性名前加单下划线:
Python 没有真正的私有方法。相反,在方法名或属性名前加一个下划线表示你不应该访问这个方法,因为它不是 API 的一部分。
class BaseForm(StrAndUnicode):
def _get_errors(self):
"Returns an ErrorDict for the data provided for the form"
if self._errors is None:
self.full_clean()
return self._errors
errors = property(_get_errors)
(此代码片段摘自Django源代码:django/forms/forms.py)。在此代码中,errors
是公共属性,但此属性调用的方法 _get_errors
是“私有的”,因此您不应访问它。
双下划线开头:
这会引起很多混淆。它不应用于创建私有方法。它应该用于避免您的方法被子类覆盖或意外访问。让我们看一个例子:
class A(object):
def __test(self):
print "I'm a test method in class A"
def test(self):
self.__test()
a = A()
a.test()
# a.__test() # This fails with an AttributeError
a._A__test() # Works! We can access the mangled name directly!
输出:
$ python test.py
I'm test method in class A
I'm test method in class A
class B(A):
def __test(self):
print "I'm test method in class B"
b = B()
b.test()
输出将会是...
$ python test.py
I'm test method in class A
正如我们所看到的,B.test() 没有调用 B.__test() 方法,这可能是我们期望的。但事实上,这是__的正确行为。两个调用 __test() 方法的方法会自动重命名(混淆)为 _A__test() 和 _B__test(),因此它们不会被意外覆盖。当创建一个以__开头的方法时,这意味着您不希望任何人能够覆盖它,并且只想从其自身的类中访问它。
在开头和结尾处使用两个下划线:
当我们看到像 __this__ 这样的方法时,请不要调用它。这是 Python 要调用的方法,而不是您。让我们来看一下:
>>> name = "test string"
>>> name.__len__()
11
>>> len(name)
11
>>> number = 10
>>> number.__add__(40)
50
>>> number + 50
60
总是有一个运算符或本地函数调用这些魔术方法。有时它只是Python在特定情况下调用的钩子。例如,当对象在调用__new__()
构建实例后创建时,会调用__init__()
...
让我们举个例子...
class FalseCalculator(object):
def __init__(self, number):
self.number = number
def __add__(self, number):
return self.number - number
def __sub__(self, number):
return self.number + number
number = FalseCalculator(20)
print number + 10 # 10
print number - 20 # 40
_var
): 命名约定表明该名称仅供内部使用。通常不由Python解释器执行(除了通配符导入),仅作为对程序员的提示。var_
): 按惯例用于避免与Python关键字冲突的命名。__var
): 在类上下文中使用时触发名称混淆。由Python解释器强制执行。__var__
): 表示Python语言定义的特殊方法。请避免使用此命名方案来命名自己的属性。_
): 有时用作临时或无关紧要变量的名称("不关心")。还可以用作PythonREPL中最后一个表达式的结果。由于很多人都在参考雷蒙德的演讲,因此我将把他说的话简单写下来:
The intention of the double underscores was not about privacy. The intention was to use it exactly like this
class Circle(object): def __init__(self, radius): self.radius = radius def area(self): p = self.__perimeter() r = p / math.pi / 2.0 return math.pi * r ** 2.0 def perimeter(self): return 2.0 * math.pi * self.radius __perimeter = perimeter # local reference class Tire(Circle): def perimeter(self): return Circle.perimeter(self) * 1.25
It's actually the opposite of privacy, it's all about freedom. It makes your subclasses free to override any one method without breaking the others.
假设你在Circle
中没有保留perimeter
的本地引用。现在,派生类Tire
覆盖了perimeter
的实现,而没有触及area
。当你调用Tire(5).area()
时,在理论上它仍然应该使用Circle.perimeter
进行计算,但实际上它正在使用Tire.perimeter
,这不是预期的行为。这就是为什么我们需要在Circle
中有一个本地引用。
但为什么要使用__perimeter
而不是_perimeter
?因为_perimeter
仍然给派生类覆盖的机会:
class Tire(Circle):
def perimeter(self):
return Circle.perimeter(self) * 1.25
_perimeter = perimeter
双下划线会进行名称重整,因此父类中的本地引用很小概率会在派生类中被覆盖。 因此,“使您的子类可以自由地重写任何一个方法而不破坏其他方法”。
如果您的类不会被继承,或者方法重写不会破坏任何内容,则根本不需要使用__double_leading_underscore
。
def foo(bar):
return _('my_' + bar)
在这种情况下,_()是一个本地化函数的别名,它会根据语言环境将文本转换为适当的语言等。例如,Sphinx就是这样做的,在导入中可以找到该函数。from sphinx.locale import l_, _
在sphinx.locale中,_()被赋值为某个本地化函数的别名。
gettext
模块描述了这种模式:“gettext通过在翻译数据库中查找字面字符串并提取相应的翻译字符串来工作。通常的模式是将适当的查找函数绑定到名称“_”(一个下划线字符),以便代码不会被大量调用具有较长名称的函数所淹没。”也许这就是sphinx在“幕后”所做的事情;类似于示例t = gettext.translation('example_domain', 'locale', fallback=True); _ = t.gettext
。 - Nate Anderson_
本地化函数的导入或创建。我意识到我的项目使用了gettext.install(...)
,它会"将函数 _() 安装在Python的内置命名空间中...",这是gettext
模块的"基于类的API"的一种技术(与直接使用API gettext.gettext(...)
相比)。关于内置命名空间和模块全局命名空间的更多信息,请参阅此处。 - Nate Anderson_var
:在Python中,以单个下划线开头的变量是经典变量,旨在通知使用您的代码的其他人,此变量应保留供内部使用。它们与经典变量有一个不同之处:在导入定义它们的对象/模块的通配符导入时,它们不会被导入(当定义__all__
变量时除外)。例如:
# foo.py
var = "var"
_var = "_var"
# bar.py
from foo import *
print(dir()) # list of defined objects, contains 'var' but not '_var'
print(var) # var
print(_var) # NameError: name '_var' is not defined
_
:单下划线是前导单下划线变量的特例。按照约定,它被用作废弃变量,存储不打算后续访问的值。 它也不会被通配符导入。例如:这个 for 循环打印"我不能在课堂上讲话" 10次,永远不需要访问
_
变量。
<code>for _ in range(10):
print("I must not talk in class")
</code>
var_
: 以单个下划线结尾的变量。它们是经典变量,按照惯例用来避免与Python关键字冲突。例如:
class_ = "MyClassName"
__var
:双下划线变量(至少两个前导下划线,最多一个尾随下划线)。当用作类属性(变量和方法)时,这些变量会被重命名:在类的外部,Python 将属性重命名为 _<Class_name>__<attribute_name>
。例如:
class MyClass:
__an_attribute = "attribute_value"
my_class = MyClass()
print(my_class._MyClass__an_attribute) # "attribute_value"
print(my_class.__an_attribute) # AttributeError: 'MyClass' object has no attribute '__an_attribute'
在类外使用时,它们的行为类似于单个前导下划线变量。
__var__
: 双前导和双尾随下划线变量(至少两个前导和尾随下划线)。也称为dunders。Python使用此命名约定来内部定义变量。请避免使用此约定以防止名称冲突,可能会随着Python更新而出现。Dunder变量的行为类似于单个前导下划线变量:当在类中使用时,它们不受名称混淆的影响,但是不会在通配符导入中导入。
如果想要让变量只读,个人认为最好的方式是使用property(),只传入getter。使用property()可以完全控制数据。
class PrivateVarC(object):
def get_x(self):
pass
def set_x(self, val):
pass
rwvar = property(get_p, set_p)
ronly = property(get_p)
我知道OP提出了一个有些不同的问题,但既然我找到了另一个关于“如何设置私有变量”的重复问题,我想在这里添加一些额外的信息。
propeety
作为方法装饰器会更加简洁。 - TheLizzard