Python私有化及_下划线命名用途

简介: Python中没有真正的私有属性或方法,没有真正的私有化,但有一些和命名有关的约定,让编程人员处理一些需要私有化的情况,我们常常需要区分私有方法、属性和公有方法、属性以方便管理和调用。那么在Python中如何做呢?

引言

Python中没有真正的私有属性或方法,没有真正的私有化,但有一些和命名有关的约定,让编程人员处理一些需要私有化的情况,我们常常需要区分私有方法、属性和公有方法、属性以方便管理和调用。那么在Python中如何做呢?

在变量、方法命名中有下列几种情况:

  • xx 公有变量/方法
  • _xx 前置单下划线
  • __xx 前置双下划线
  • __xx__ 前后双下划线
  • xx_ 后置单下划线

接下来分别介绍这几种带 下划线 命名的特性与区别。

<br/>

_单前置下划线

前置单下划线的意思是 提示 其他程序员,以单下划线开头的变量或方法只在内部使用。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()

         

<br/>

运行结果:

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

<br/>

# 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()

<br/>

在外部访问直接访问私有属性或方法

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')

<br/>

重写 __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',
 ...
 ...
]

<br/>

调用 run() 方法的时候依然执行的是 Foo 类的 __method() 方法,因为在 run() 方法的实现中,self.__method() 已自动变形为 self._Foo__method()Goo 继承的 run() 方法也是如此,而 __method()方法就变成了 _Goo__method()

名字重整 name mangling 的技术,又叫 name decoration命名修饰。在很多现代编程语言中,这一技术用来解决 需要唯一名称而引起的问题,比如命名冲突/重载等。

<br/>

\_\_前后双下划线__

前后均带双下划线的命名,一般用于特殊方法的命名,用来实现对象的一些行为或者功能,比如 __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()

<br/>

测试结果:

__add__() execute
7
__getitem__() execute
6
__len__() execute
1
__str__() execute
< 6 >
[Finished in 0.1s]

<br/>

后置单下划线_

后置单下划线,用于避免与Python关键词的冲突。如下:

list_ = ["wang", "hui", "zack"]
dict_ = {
    "name": "hui",
    "age": 21
}

<br/>

总结

  • _名 的变量、函数、类在使用 from xxx import * 时都不会被导入。
  • __名字的实例属性、方法会被 名字重整 name mangling >>> _类名__属性名
  • 父类中属性名为 __名字 的,子类不继承,子类不能访问。
  • 如果在子类中向 __名字 赋值,那么会在子类中定义的一个与父类相同名字的属性。
  • __xx__ 魔法对象或属性,有着特殊作用。不要随意起这种命名。
  • xx_ 用于避免与Python关键词的冲突。

<br/>

公众号

新建文件夹X

大自然用数百亿年创造出我们现实世界,而程序员用几百年创造出一个完全不同的虚拟世界。我们用键盘敲出一砖一瓦,用大脑构建一切。人们把1000视为权威,我们反其道行之,捍卫1024的地位。我们不是键盘侠,我们只是平凡世界中不凡的缔造者 。
相关文章
|
2月前
|
存储 索引 Python
Python小技巧:单下划线 '_' 原创
Python小技巧:单下划线 '_' 原创
|
5月前
|
程序员 Python
【随手记】python中各类下划线的作用与功能
【随手记】python中各类下划线的作用与功能
69 0
|
6月前
|
开发工具 Python
Python 中下划线的 6 个作用(1),【干货】
Python 中下划线的 6 个作用(1),【干货】
WK
|
23天前
|
Python
Python类命名
在Python编程中,类命名至关重要,影响代码的可读性和维护性。建议使用大写驼峰命名法(如Employee),确保名称简洁且具描述性,避免使用内置类型名及单字母或数字开头,遵循PEP 8风格指南,保持项目内命名风格一致。
WK
10 0
WK
|
23天前
|
Python
Python变量命名
在Python编程中,变量命名对代码的可读性和维护性至关重要。遵循PEP 8风格指南,变量名应使用小写字母和下划线分隔单词,保持简洁明了、描述性强,避免使用单字母、Python关键字和内置函数名,采用有意义的缩写,使用英文命名,保持命名风格一致,避免魔法数字,考虑上下文。正确示例:`user_name`、`order_quantity`;不正确示例:`n`、`q`。
WK
16 0
WK
|
23天前
|
Python
Python函数命名
在Python中,函数命名应清晰、简洁且易于理解。遵循PEP 8风格指南,使用小写字母和下划线分隔单词,避免单字母命名和保留字。函数名应描述功能,以动词开头,避免泛化名称,使用有意义的缩写,保持命名风格一致,避免魔法数字。示例包括 `calculate_area_of_circle`、`fetch_data_from_api` 和 `save_file_to_disk`。这些实践有助于创建易读、易维护的代码。
WK
16 0
|
2月前
|
Python
Python变量用法——单下划线变量名_ 原创
Python变量用法——单下划线变量名_ 原创
|
2月前
|
IDE 开发工具 Python
python3代码编程规范(命名、空格、注释、代码布局、编程建议等)
该文章详细介绍了Python3的编程规范,包括命名、空格使用、注释、代码布局等方面的最佳实践,帮助提升代码的可读性和一致性。
37 0
|
3月前
|
存储 IDE 开发工具
Python中变量命名规则
【8月更文挑战第5天】
79 4
|
3月前
|
存储 Python
python变量命名规则
【8月更文挑战第4天】
68 5