[转载] Python学习系列之下划线与变量命名规则

参考链接: Python中的命名空间和范围

一、定义

1.1 从入口函数谈起

从其他语言比如C++和Java,转到Python的同学经常遇到这个问题,入口函数是什么?Python如何执行?

后来我们发现了:

if __name__ == 'main':

但是为什么这样约定?__name__又是什么意思?这就涉及了Python中变量和函数的命名规则了。涉及单下划线和双下划线("dunder"),名称修饰(name mangling)等。

1.2 变量命名

变量名(标识符)是Python的一种原子元素。当变量名被绑定到一个对象的时候,变量名就指代这个对象。当变量名出现在代码块中,那它就是本地变量;当变量名出现在模块中,它就是全局变量。

1.3 下划线

Python 用下划线作为变量前缀和后缀指定特殊变量/方法。单下划线和双下划线在Python变量和方法名称中都各有其含义。有一些含义仅仅是依照约定,被视作是对程序员的提示;而有一些含义是由Python解释器严格执行的。

用于特殊变量的表示,访问控制等作用,作为用户则需要谨慎。核心原则是避免用下划线作为类外变量名的开始。类内则需要按照需要去选择,但依旧不能用__object__ 形式。

因为下划线对解释器有特殊的意义,而且是内建标识符所使用的符号,我们建议程序员避免用下划线作为变量名的开始。一般来讲,变量名_object被看作是“私有 的”,在模块或类外不可以使用,不能用'from moduleimport *'导入(但可以用常规导入方法导入)。当变量是私有的时候,用_object来表示变量是很好的习惯。因为变量名__object__对Python 来说有特殊含义,对于普通的变量应当避免这种命名风格。

主要包括:

object # public 公共的__object__ # special, python system use, user should not define like it __object # private (name mangling during runtime) 私有的 _object # obey python coding convention, consider it as private 保护的

以上私有和保护都不是严格意义的类似于Java的概念,而是Python特有的。

在本文中,我将讨论以下五种下划线模式和命名约定:

单前导下划线:_var单末尾下划线:var_双前导下划线:__var双前导和末尾下划线:__var__单下划线:_

二、单前导下划线:_var

Python中并没有真正意义上的“私有”,类的属性的的可见性取决于属性(变量和方法)的名字。

以单下划线开头的属性(例如_spam),应被当成API中非公有的部分(但是注意,它们仍然可以被访问),一般是具体实现细节的部分,修改它们时无需对外部通知,需要提供外部接口以供外部调用。在类中,带有前导下划线的名称只是向其他程序员指示该属性或方法旨在是私有的。

引用PEP-8:


_single_leading_underscore: weak “internal use” indicator. E.g. from M import * does not import objects whose name starts with an underscore.


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属性是对外API的一部分,如果你想获取错误详情,应该访问errors属性,而不是(也不应该)访问_get_errors方法。

(1)约定的内部变量

当涉及到变量和方法名称时,单个下划线前缀有一个约定俗成的含义。 它是对程序员的一个提示 ,意味着Python社区一致认为它应该是什么意思,但程序的行为不受影响。下划线前缀的含义是告知其他程序员:以单个下划线开头的变量或方法仅供内部使用。 该约定在PEP 8中有定义。但这不是Python强制规定的。 Python不像Java那样在“私有”和“公共”变量之间有很强的区别。

看看下面的例子:

class Test:

def __init__(self):

self.foo = 11

self._bar = 23

如果你实例化此类,并尝试访问在__init__构造函数中定义的foo和_bar属性,会发生什么情况? 让我们来看看:

>>> t = Test()

>>> t.foo

11

>>> t._bar

23

你会看到_bar中的单个下划线并没有阻止我们“进入”类并访问该变量的值。这是因为Python中的单个下划线前缀仅仅是一个约定,而不是一个强制性的规则。至少相对于变量和方法名而言。

其可以直接访问。不过根据python的约定,应该将其视作private,而不要在外部使用它们,良好的编程习惯是不要在外部使用它。

(2)模块级私有化

单下划线常用来实现模块级私有化,当我们使用“from mymodule import *”来加载模块的时候,不会加载以单下划线开头的模块属性。也就是前导下划线的确会影响从模块中导入名称的方式。

假设你在一个名为my_module的模块中有以下代码:

# This is my_module.py:

def external_func():

return 23

def _internal_func():

return 42

现在,如果使用通配符从模块中导入所有名称,则Python不会导入带有前导下划线的名称(除非模块定义了覆盖此行为的__all__列表,模块或包中的__all__列表显式地包含了他们):

>>> from my_module import *

>>> external_func()

23

>>> _internal_func()

NameError: "name '_internal_func' is not defined"

顺便说一下,应该避免通配符导入,因为它们使名称空间中存在哪些名称不清楚,存在重名的变量会出现混乱。 为了清楚起见,坚持常规导入更好。

与通配符导入不同,常规导入不受前导单个下划线命名约定的影响:

>>> import my_module

>>> my_module.external_func()

23

>>> my_module._internal_func()

42

我知道这一点可能有点令人困惑。 如果你遵循PEP 8推荐,避免通配符导入,那么你真正需要记住的只有这个:


单个下划线是一个Python命名约定,表示这个名称是供内部使用的。 它通常不由Python解释器强制执行,仅仅作为一种对程序员的提示。


三、单末尾下划线:var_

有时候,一个变量的最合适的名称已经被一个关键字所占用。 因此,像class或def这样的名称不能用作Python中的变量名称。 在这种情况下,你可以附加一个下划线来解决命名冲突:

>>> def make_object(name, class):

SyntaxError: "invalid syntax"

>>> def make_object(name, class_):

...     pass

总之,单个末尾下划线(后缀)是一个约定,用来避免与Python关键字产生命名冲突。 PEP 8解释了这个约定。

四、双前导下划线:__var

双下划线用在Python类变量中是Name Mangling。这种特定的行为差不多等价于Java中的final方法(不能被挤成)和C++中的正常方法(非虚方法)。之前很多人说Python中双下划线开头表示私有。这样理解可能也不能说错,但这不是Python设计双下划线开头的初衷和目的,Python设计此的真正目的仅仅是为了避免子类覆盖父类的方法。

《Python学习手册》的说明,以双下划线开头的变量名,会自动扩张(原始的变量名会在头部加入一个下划线,然后是所在类名称),从而包含了所在类的名称。无法被继承,也无法在外部访问。必须通过改写后的变量名或函数名来访问。更像是私有变量和私有函数。当然也不是静态变量。

(1)防止子类重写父类属性

以双下划线开头并最多以一个下划线结尾的变量或函数(例如___spam),将会被替换成_classname__spam这样的形式,其中classname是当前类的类名。知道了这点之后,我们仍然可以访问到python的私有函数

从the Python docs:


Any identifier of the form __spam (at least two leading underscores, at most one trailing underscore) is textually replaced with _classname__spam, where classname is the current class name with leading underscore(s) stripped. This mangling is done without regard to the syntactic position of the identifier, so it can be used to define class-private instance and class variables, methods, variables stored in globals, and even variables stored in instances. private to this class on instances of other classes.


和来自同一页面的警告:


Name mangling is intended to give classes an easy way to define “private” instance variables and methods, without having to worry about instance variables defined by derived classes, or mucking with instance variables by code outside the class. Note that the mangling rules are designed mostly to avoid accidents; it still is possible for a determined soul to access or modify a variable that is considered private.


到目前为止,我们所涉及的所有命名模式的含义,来自于已达成共识的约定。 而对于以双下划线开头的Python类的属性(包括变量和方法),情况就有点不同了。

双下划线前缀会导致Python解释器重写属性名称,以避免子类中的命名冲突。这也叫做名称修饰(name mangling) - 解释器更改变量的名称,以便在类被扩展的时候不容易产生冲突。

我知道这听起来很抽象。 因此,我组合了一个小小的代码示例来予以说明:

class Test:

def __init__(self):

self.foo = 11

self._bar = 23

self.__baz = 23

让我们用内置的dir()函数来看看这个对象的属性:

>>> t = Test()

>>> dir(t)

['_Test__baz', '__class__', '__delattr__', '__dict__', '__dir__',

'__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',

'__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__',

'__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',

'__setattr__', '__sizeof__', '__str__', '__subclasshook__',

'__weakref__', '_bar', 'foo']

以上是这个对象属性的列表。 让我们来看看这个列表,并寻找我们的原始变量名称foo,_bar和__baz - 我保证你会注意到一些有趣的变化。

self.foo变量在属性列表中显示为未修改为foo。self._bar的行为方式相同 - 它以_bar的形式显示在类上。 就像我之前说过的,在这种情况下,前导下划线仅仅是一个约定。 给程序员一个提示而已。然而,对于self.__baz而言,情况看起来有点不同。 当你在该列表中搜索__baz时,你会看不到有这个名字的变量。

__baz出什么情况了?

如果你仔细观察,你会看到此对象上有一个名为_Test__baz的属性。 这就是Python解释器所做的名称修饰。 它这样做是为了防止变量在子类中被重写。

让我们创建另一个扩展Test类的类,并尝试重写构造函数中添加的现有属性:

class ExtendedTest(Test):

def __init__(self):

super().__init__()

self.foo = 'overridden'

self._bar = 'overridden'

self.__baz = 'overridden'

现在,你认为foo,_bar和__baz的值会出现在这个ExtendedTest类的实例上吗? 我们来看一看:

>>> t2 = ExtendedTest()

>>> t2.foo

'overridden'

>>> t2._bar

'overridden'

>>> t2.__baz

AttributeError: "'ExtendedTest' object has no attribute '__baz'"

等一下,当我们尝试查看t2 .__ baz的值时,为什么我们会得到AttributeError? 名称修饰被再次触发了! 事实证明,这个对象甚至没有__baz属性:

>>> dir(t2)

['_ExtendedTest__baz', '_Test__baz', '__class__', '__delattr__',

'__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',

'__getattribute__', '__gt__', '__hash__', '__init__', '__le__',

'__lt__', '__module__', '__ne__', '__new__', '__reduce__',

'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',

'__subclasshook__', '__weakref__', '_bar', 'foo', 'get_vars']

正如你可以看到__baz变成_ExtendedTest__baz以防止意外修改:

>>> t2._ExtendedTest__baz

'overridden'

但原来的_Test__baz还在:

>>> t2._Test__baz

42

双下划线名称修饰对程序员是完全透明的。 下面的例子证实了这一点:

class ManglingTest:

def __init__(self):

self.__mangled = 'hello'

def get_mangled(self):

return self.__mangled

>>> ManglingTest().get_mangled()

'hello'

>>> ManglingTest().__mangled

AttributeError: "'ManglingTest' object has no attribute '__mangled'"

名称修饰是否也适用于方法名称? 是的,也适用。名称修饰会影响在一个类的上下文中,以两个下划线字符("dunders")开头的所有名称:

class MangledMethod:

def __method(self):

return 42

def call_it(self):

return self.__method()

>>> MangledMethod().__method()

AttributeError: "'MangledMethod' object has no attribute '__method'"

>>> MangledMethod().call_it()

42

这是另一个也许令人惊讶的运用名称修饰的例子:

_MangledGlobal__mangled = 23

class MangledGlobal:

def test(self):

return __mangled

>>> MangledGlobal().test()

23

在这个例子中,我声明了一个名为_MangledGlobal__mangled的全局变量。然后我在名为MangledGlobal的类的上下文中访问变量。由于名称修饰,我能够在类的test()方法内,以__mangled来引用_MangledGlobal__mangled全局变量。 Python解释器自动将名称__mangled扩展为_MangledGlobal__mangled,因为它以两个下划线字符开头。这表明名称修饰不是专门与类属性关联的。它适用于在类上下文中使用的两个下划线字符开头的任何名称。

(2)示例

class A(object):

def __method(self):

print("I'm a method in class A")

def method_x(self):

print("I'm another method in class A\n")

def method(self):

self.__method()

self.method_x()

class B(A):

def __method(self):

print("I'm a method in class B")

def method_x(self):

print("I'm another method in class B\n")

if __name__ == '__main__':


print("situation 1:")

a = A()

a.method()

b = B()

b.method()

print("situation 2:")

# a.__method()

a._A__method()

执行结果:

situation 1:

I'm a method in class A

I'm another method in class A

I'm a method in class A

I'm another method in class B

situation 2:

I'm a method in class A

这里有两个点需要注意:

A类中我们定义了__method()、method_x和method()三个方法;然后我们重新定义一个类B,继承自A,并且在B类中覆写(override)了其父类的__method()和method_x方法,但是从输出结果看,B对象调用method()方法时调用了其父类A的__method()方法和自己的method_x()方法。也就是说,__method()覆写没有生效,而method_x()覆写生效了。而这也正是Python设计双下划线开头的唯一目的。

前面我们就说了,Python中不存在真正意义上的私有变量。对于双下划线开头的方法和属性虽然我们不能直接引用,那是因为Python默认在其前面加了前缀_类名,所以就像situation 2下面的代码,虽然我们不能用a直接访问__method(),但却可以加上前缀去访问,即_A__method()。

Python的私有变量轧压:

Python把以两个或以上下划线字符开头且没有以两个或以上下划线结尾的变量当作私有变量。私有变量会在代码生成之前被转换为长格式(变为公有)。转换机制是这样的:在变量前端插入类名,再在前端加入一个下划线字符。这就是所谓的私有变量轧压(Private name mangling)。

注意: 一是因为轧压会使标识符变长,当超过255的时候,Python会切断,要注意因此引起的命名冲突。 二是当类名全部以下划线命名(如___)的时候,Python就不再执行轧压。

具体原因: 因为类A定义了一个私有成员函数(变量),所以在代码生成之前先执行私有变量轧压。轧压之后,类A的代码就变成这样了:

class A(object):

def _A__method(self): # 这行变了

print("I'm a method in class A")

def method_x(self):

print("I'm another method in class A\n")

def method(self):

self._A__method() # 这行变了

self.method_x()

有点像C语言里的宏展开。因为在类B定义的时候没有覆盖__init__方法,所以调用的仍然是A.__init__,即执行了self._A__method(),自然输出“I'm a method in class A”了。

下面的两段代码可以增加说服力,增进理解:

class A(object):

def __init__(self):

self.__private()

self.public()

def __private(self):

print 'A.__private()'

def public(self):

print 'A.public()'

class B(A):

def __private(self):

print 'B.__private()'

def public(self):

print 'B.public()'

b = B()

A.__private()

B.public()

原因与上述相同。类 A里的__private标识符将被转换为_A__private,这就是_A__private和__private消失的原因了。

class C(A):

def __init__(self):          # 重写 __init__ ,不再调用 self._A__private

self.__private()       # 这里绑定的是 _C_private

self.public()

def __private(self):

print 'C.__private()'

def public(self):

print 'C.public()'

c = C()

C.__private()

C.public()

可以看出重写 __init__ ,不再调用 self._A__private,而是调用类C里__init__函数里绑定的_C_private。

而且在类A中也可以直接调用self._A__method(),虽然该变量未定义,但是进行命名改编时会修改。

五、双前导和末尾下划线:__var__

如果一个名字同时以双下划线开始和结束,则不会应用名称修饰。 由双下划线前缀和后缀包围的变量不会被Python解释器修改:

class PrefixPostfixTest:

def __init__(self):

self.__bam__ = 42

>>> PrefixPostfixTest().__bam__

42

但是,Python保留了有双前导和双末尾下划线的名称,用于特殊用途。 前后有双下划线表示的是特殊函数。通常可以复写这些方法实现自己所需要的功能。

这样的例子有,__init__ --- 对象构造函数,__call__ --- 它使得一个对象可以被调用。表示这是Python自己调用的,程序员不要调用。比如我们可以调用len()函数来求长度,其实它后台是调用了__len__()方法。一般我们应该使用len,而不是直接使用__len__()。

我们一般称__len__()这种方法为magic methods(魔术方法),一些操作符后台调用的也是也是这些magic methods,比如+后台调用的是__add__,-调用的是__sub__,所以这种机制使得我们可以在自己的类中覆写操作符(见后面例子)。另外,有的时候这种开头结尾双下划线方法仅仅是某些特殊场景的回调函数,比如__init__()会在对象的初始化时调用,__new__()会在构建一个实例的时候调用等等。

类内置函数和全局内置函数标识符等的实现细节回调函数

class CrazyNumber(object):

def __init__(self, n):

self.n = n

def __add__(self, other):

return self.n - other

def __sub__(self, other):

return self.n + other

def __str__(self):

return str(self.n)

num = CrazyNumber(10)

print(num) # output is: 10

print(num + 5) # output is: 5

print(num - 20) # output is: 30

在上面这个例子中,我们重写了+和-操作符,将他们的功能交换了。


六、单下划线:_

(1)临时变量

按照习惯,有时候单个独立下划线是用作一个名字,来表示某个变量是临时的或无关紧要的。作为临时性的名称使用,但是在后面不会再次用到该名称。这种用法在循环中会经常用到。

例如,在下面的循环中,我们不需要访问正在运行的索引,我们可以使用“_”来表示它只是一个临时值:

>>> for _ in range(32):

...     print('Hello, World.')

你也可以在拆分(unpacking)表达式(对元组以逗号分隔开的表达式)中将单个下划线用作“不关心的”变量,以忽略特定的值。 同样,这个含义只是“依照约定”,并不会在Python解释器中触发特殊的行为。 单个下划线仅仅是一个有效的变量名称,会有这个用途而已。

在下面的代码示例中,我将汽车元组拆分为单独的变量,但我只对颜色和里程值感兴趣。 但是,为了使拆分表达式成功运行,我需要将包含在元组中的所有值分配给变量。 在这种情况下,“_”作为占位符变量可以派上用场:

>>> car = ('red', 'auto', 12, 3812.4)

>>> color, _, _, mileage = car

>>> color

'red'

>>> mileage

3812.4

>>> _

12

(2)_代表交互式解释器会话中上一条的执行结果

除了用作临时变量之外,“_”是大多数Python REPL中的一个特殊变量,它表示由解释器执行的最近一个表达式的结果。这种用法有点类似于Linux中的上一条命令的用法。只不过在在Python解释器中表示的上一条执行的结果。这种用法首先被标准CPython解释器采用,然后其他类型的解释器也先后采用。

这样就很方便了,比如你可以在一个解释器会话中访问先前计算的结果,或者,你是在动态构建多个对象并与它们交互,无需事先给这些对象分配名字:

>>> 20 + 3

23

>>> _

23

>>> print(_)

23

>>> list()

[]

>>> _.append(1)

>>> _.append(2)

>>> _.append(3)

>>> _

[1, 2, 3]

(3)国际化

也许你也曾看到”_“会被作为一个函数来使用。这种情况下,它通常用于实现国际化和本地化字符串之间翻译查找的函数名称,这似乎源自并遵循相应的C约定。

from django.utils.translation import ugettext as _

from django.http import HttpResponse

def my_view(request):

output = _("Welcome to my site.")

return HttpResponse(output)

场景一和场景三中的使用方法可能会相互冲突,所以我们需要避免在使用“_”作为国际化查找转换功能的代码块中同时使用“_”作为临时名称。

七、访问控制

在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。

但是,从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的name、score属性:

>>> bart = Student('Bart Simpson', 98)

>>> bart.score

98

>>> bart.score = 59

>>> bart.score

59


如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student类改一改:

class Student(object):

def __init__(self, name, score):

self.__name = name

self.__score = score

def print_score(self):

print '%s: %s' % (self.__name, self.__score)


改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.__name和实例变量.__score了:

>>> bart = Student('Bart Simpson', 98)

>>> bart.__name

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

AttributeError: 'Student' object has no attribute '__name'


这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。

但是如果外部代码要获取name和score怎么办?可以给Student类增加get_name和get_score这样的方法(相当于Java的getter和setter方法):

class Student(object):

...

def get_name(self):

return self.__name

def get_score(self):

return self.__score


如果又要允许外部代码修改score怎么办?可以给Student类增加set_score方法:

class Student(object):

...

def set_score(self, score):

self.__score = score


你也许会问,原先那种直接通过bart.score = 59也可以修改啊,为什么要定义一个方法大费周折?因为在方法中,可以对参数做检查,避免传入无效的参数:

class Student(object):

...

def set_score(self, score):

if 0 <= score <= 100:

self.__score = score

else:

raise ValueError('bad score')


需要注意的是,在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__、__score__这样的变量名。

有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。

双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量:

>>> bart._Student__name

'Bart Simpson'


但是强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名。

总的来说就是,Python本身没有任何机制阻止你干坏事,一切全靠自觉。

访问限制

阅读: 226345

在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。

但是,从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的name、score属性:

>>> bart = Student('Bart Simpson', 59)

>>> bart.score

59

>>> bart.score = 99

>>> bart.score

99


如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student类改一改:

class Student(object):

def __init__(self, name, score):

self.__name = name

self.__score = score

def print_score(self):

print('%s: %s' % (self.__name, self.__score))


改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.__name和实例变量.__score了:

>>> bart = Student('Bart Simpson', 59)

>>> bart.__name

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

AttributeError: 'Student' object has no attribute '__name'


这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。

但是如果外部代码要获取name和score怎么办?可以给Student类增加get_name和get_score这样的方法:

class Student(object):

...

def get_name(self):

return self.__name

def get_score(self):

return self.__score


如果又要允许外部代码修改score怎么办?可以再给Student类增加set_score方法:

class Student(object):

...

def set_score(self, score):

self.__score = score


你也许会问,原先那种直接通过bart.score = 99也可以修改啊,为什么要定义一个方法大费周折?因为在方法中,可以对参数做检查,避免传入无效的参数:

class Student(object):

...

def set_score(self, score):

if 0 <= score <= 100:

self.__score = score

else:

raise ValueError('bad score')


需要注意的是,在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__、__score__这样的变量名。

有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。

双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量:

>>> bart._Student__name

'Bart Simpson'


但是强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名。

总的来说就是,Python本身没有任何机制阻止你干坏事,一切全靠自觉。

最后注意下面的这种错误写法:

>>> bart = Student('Bart Simpson', 59)

>>> bart.get_name()

'Bart Simpson'

>>> bart.__name = 'New Name' # 设置__name变量!

>>> bart.__name

'New Name'


表面上看,外部代码“成功”地设置了__name变量,但实际上这个__name变量和class内部的__name变量不是一个变量!内部的__name变量已经被Python解释器自动改成了_Student__name,而外部代码给bart新增了一个__name变量。不信试试:

>>> bart.get_name() # get_name()内部返回self.__name

'Bart Simpson'

八、总结

(1)除了上述用法,下划线还作为函数的连接符,仅仅是一种函数名的命名方式,就如同Java的驼峰式的命名法是一样的。

(2)python有对private的描述,但python中不存在protected的概念,要么是public要么就是private,但是python中的private不像C++, Java那样,它并不是真正意义上的private,通过name mangling(名称改编(目的就是以防子类意外重写基类的方法或者属性),即前面加上“单下划线”+类名,eg:_Class__object)机制就可以访问private了。

"单下划线" 开始的成员变量叫做保护变量,意思是只有类对象和子类对象自己能访问到这些变量;"双下划线" 开始的是私有成员,意思是只有类对象自己能访问,连子类对象也不能访问到这个数据。

(3)

使用单下划线(_one_underline)开头表示方法不是API的一部分,不要直接访问(虽然语法上访问也没有什么问题)。使用双下划线开头(__two_underlines)开头表示子类不能覆写该方法。除非你真的知道你在干什么,否则不要使用这种方式。当你想让自己定义的对象也可以像Python内置的对象一样使用Python内置的一些函数或操作符(比如len、add、+、-、==等)时,你可以定义该类方法。当然还有些属性只在末尾加了但下划线,这仅仅是为了避免我们起的一些名字和Python保留关键字冲突,没有特殊含义。

以下是一个简短的小结,即“速查表”,罗列了我在本文中谈到的五种Python下划线模式的含义:

© 著作权归作者所有,转载或内容合作请联系作者

推荐阅读 更多精彩内容