Python私有化及_下划线命名用途
引言
Python中没有真正的私有属性或方法,没有真正的私有化,但有一些和命名有关的约定,让编程人员处理一些需要私有化的情况,我们常常需要区分私有方法、属性和公有方法、属性以方便管理和调用。那么在Python中如何做呢?
在变量、方法命名中有下列几种情况:
xx公有变量/方法_xx前置单下划线__xx前置双下划线__xx__前后双下划线xx_后置单下划线
接下来分别介绍这几种带 下划线 命名的特性与区别。
_单前置下划线
前置单下划线的意思是 提示 其他程序员,以单下划线开头的变量或方法只在内部使用。PEP 8 中定义了这个约定( PEP 8 是最常用的 Python 代码风格指南)。
不过,这个约定对Python解释器并没有特殊含义。与Java不同,Python在 “私有” 和 “公共” 变量之间并没有很强的区别。在变量名之前添加一个下划线更像是有人挂出了一个小小的下划线警告标志:“注意,这并不是这个类的公共接口。最好不要使用它。”
一般Python约定前置单下划线 _ 的属性和方法为私有方法或属性,以提示该属性和方法 不应 在外部调用。
当然,在类中也可以用单下划线开头来命名属性或者方法,这只是表示类的定义者希望这些属性或者方法是 "私有的",但实际上并不会起任何作用。例如:
# coding:utf8
class Sister():
    def __init__(self, name, age, telphone):
        self.name = name
        
        # 不建议被外界使用
        self._age = age 
        self._telphone = telphone
    # 允许被外界调用
    def show_age(self):
        print("show_age() called")
        print("小姐姐你真好看,有种独特的气质,不知芳龄多少?")
        print(self._age)
        if self._age > 18:
            print("小姐姐看起来像18岁小仙女")
        else:
            print("说真的小姐姐真的好漂亮")
        print("是吗 (✪ω✪)\n...")
        print("name: %s, phone: %s" % (self.name, self._telphone))
        print("end...\n")
        return self._age
    # 不建议被外界调用
    def _get_age(self):
        print("_get_age() called")
        print("阿姨你今年多大啊?")
        print("18你信不!!!")
        print("end...\n")
        return self._age
def main():
    s = Sister('Mary', 20, 5201314)
    s.show_age()
    # 可以调用但不建议
    s._get_age()
    print(s.name)
    print(s._age)
    print(s._telphone)
if __name__ == '__main__':
    main()
         
运行结果:
show_age() called
小姐姐你真好看,有种独特的气质,不知芳龄多少?
20
小姐姐看起来像18岁小仙女
是吗 (✪ω✪)
...
name: Mary, phone: 5201314
end...
_get_age() called
阿姨你今年多大啊?
18你信不!!!
end...
Mary
20
5201314
[Finished in 0.1s]
可以看到,前面的单下划线 _ 并没有阻止我们进入这个类访问变量的值。
这是因为 Python中的前置单下划线只是一个公认的约定,至少在涉及变量名和方法名时是这样的。
但是前置下划线会影响从模块中导入名称的方式,不会被 from somemodule import * 导入。
# demo.py
_key = '123'
def _set_key(key):
 global _key
 _key = key
# test.py
from demo import *
print(_key)
_set_key('567')
对此解释器会抛出异常:NameError: name '_key' is not defined。
使用 通配符导入 从这个模块中导入所有名称,Python 不会 导入带有前置单下划线的名称(除非模块中定义了__all__ 列表覆盖了这个行为。
但并非 demo.py 中的前置单下划线变量/方法在 test.py 中就不可以使用,完全可以 import module,然后通过 module.xxx 方式,test.py 代码做如下调整:
# test.py
import demo
print(demo._key)  # 正常使用
demo._set_key('789') # 正常调用
print(demo._key)  # 正常使用
__前置双下划线
用于对象的数据封装,以此命名的属性或者方法为类的私有属性或者私有方法。
# coding:utf8
class Foo(object):
 def __init__(self):
  self__name = "private attribute"
 def getname():
  return self.__name
  
 def __method():
  print("private method")
 def run(self):
  self.__method()
在外部访问直接访问私有属性或方法
In [1]: # coding:utf8
   ...:
   ...:
   ...: class Foo(object):
   ...:
   ...:     def __init__(self):
   ...:         self.__name = "private attribute"
   ...:
   ...:     def getname(self):
   ...:         return self.__name
   ...:
   ...:     def __method(self):
   ...:         print("private method")
   ...:
   ...:     def run(self):
   ...:         self.__method()
   ...:
   ...:
In [2]: f = Foo()
In [3]: f.__name
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-3-332cfd3c796d> in <module>
----> 1 f.__name
AttributeError: 'Foo' object has no attribute '__name'
In [4]: f.__method()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-4-9cb5f81c61a6> in <module>
----> 1 f.__method()
AttributeError: 'Foo' object has no attribute '__method'
In [5]: f.getname()
Out[5]: 'private attribute'
In [6]: f.run()
private method
可以发现是不可行,这就起到了封装隐藏数据的作用。但是这种实现机制并不是很严格,机制是通过 名字重整 name mangling实现的,目的就是以防类意外重写基类的方法或属性。但类中所有以双下划线开头的名称都会自动变成 _Class_object 的新名称,如 __name  >>> _Foo__name ,我们也可以用 dir()来查看类中成员详情
In [7]: dir(f)
Out[7]:
['_Foo__method',
 '_Foo__name',
 '__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'getname',
 'run']
In [8]: f._Foo__name
Out[8]: 'private attribute'
In [9]: f._Foo__method()
private method
这种机制可以阻止继承类重新定义或者更改方法的实现,比如,定义一个 Foo 的字类 Goo:
class Goo(Foo):
 def __method(self):
  print('private method of Goo')
重写 __method 方法,运行:
In [11]: class Goo(Foo):
    ...:   def __method(self):
    ...:     print('private method of Goo')
    ...:
In [12]: g = Goo()
In [13]: g.run()
private method
In [14]: dir(g)
Out[14]:
['_Foo__method',
 '_Foo__name',
 '_Goo__method',
 ...
 ...
]
调用 run() 方法的时候依然执行的是 Foo 类的 __method() 方法,因为在 run() 方法的实现中,self.__method() 已自动变形为 self._Foo__method(),Goo 继承的 run() 方法也是如此,而 __method()方法就变成了 _Goo__method()
名字重整 name mangling 的技术,又叫 name decoration命名修饰。在很多现代编程语言中,这一技术用来解决 需要唯一名称而引起的问题,比如命名冲突/重载等。
__前后双下划线__
前后均带双下划线的命名,一般用于特殊方法的命名,用来实现对象的一些行为或者功能,比如 __new__() 方法用来创建实例,__init__() 方法用来初始化对象,x + y操作被映射为方法 x.__add__(y),序列或者字典的索引操作 x[k] 映射为x.__getitem__(k),__len__()、__str__() 分别被内置函数 len()、str()调用等等。
# coding:utf8
class Obj():
    def __init__(self, num):
        self.num = num
        self.li = list()
        self.li.append(num)
    def __add__(self, value):
        print("__add__() execute")
        return self.num + value
    def __getitem__(self, index):
        print("__getitem__() execute")
        return self.li[index]
    def __len__(self):
        print("__len__() execute")
        return len(self.li)
    def __str__(self):
        print("__str__() execute")
        return '< ' + str(self.num) + ' >'
    
def main():
    a = Obj(5)
    a = a + 2
    print(a)
    b = Obj(6)
    print(b[0])
    print(len(b))
    print(b)
if __name__ == '__main__':
    main()
测试结果:
__add__() execute
7
__getitem__() execute
6
__len__() execute
1
__str__() execute
< 6 >
[Finished in 0.1s]
后置单下划线_
后置单下划线,用于避免与Python关键词的冲突。如下:
list_ = ["wang", "hui", "zack"]
dict_ = {
    "name": "hui",
    "age": 21
}
总结
_名的变量、函数、类在使用from xxx import *时都不会被导入。__名字的实例属性、方法会被名字重整name mangling >>> _类名__属性名父类中属性名为 __名字的,子类不继承,子类不能访问。如果在子类中向 __名字赋值,那么会在子类中定义的一个与父类相同名字的属性。__xx__魔法对象或属性,有着特殊作用。不要随意起这种命名。xx_用于避免与Python关键词的冲突。
