python 下划线的多种应用场景总结


Posted in Python onMay 12, 2021

目前常见的用法有五种:

  • _用于临时变量
  • var_用于解决命名冲突问题
  • _var用于保护变量
  • __var用于私有变量
  • __var__用于魔术方法

下面我们具体看看这些下划线应用场景。

一、_用于临时变量

单下划线一般用于表示临时变量,在REPL、for循环和元组拆包等场景中比较常见。

1.1 REPL

单下划线在REPL中关联的是上一次计算的非None结果。

>>> 1+1
2
>>> _
2
>>> a=2+2
>>> _
2

1+1,结果为2,赋值给_;而赋值表达式a=2+2a为4,但整个表达式结果为None,故不会关联到_。这有点类似日常大家使用的计算器中的ANS按键,直接保存了上次的计算结果。

1.2 for循环中的_

for循环中_作为临时变量用。下划线来指代没什么意义的变量。例如在如下函数中,当我们只关心函数执行次数,而不关心具体次序的情况下,可以使用_作为参数。

nums = 13
for _ in range(nums):
    fun_oper()

1.3 元组拆包中的_

第三个用法是元组拆包,赋值的时候可以用_来表示略过的内容。如下代码忽略北京市人口数,只取得名字和区号。

>>> city,_,code = ('Beijing',21536000,'010')
>>> print(city,code)
Beijing 010

如果需要略过的内容多于一个的话,可以使用*开头的参数,表示忽略多个内容。如下代码忽略面积和人口数,只取得名字和区号

city,*_,code = ('Beijing',21536000,16410.54,'010')

1.4 国际化函数

在一些国际化编程中,_常用来表示翻译函数名。例如gettext包使用时:

import gettext
zh = gettext.tranlation('dict','locale',languages=['zh_CN'])
zh.install()
_('hello world')

依据设定的字典文件,其返回相应的汉字“你好世界”。

1.5 大数字表示形式

_也可用于数字的分割,这在数字比较长的时候常用。

>>> a = 9_999_999_999
>>> a
9999999999

a的值自动忽略了下划线。这样用_分割数字,有利于便捷读取比较大的数。

二、var_用于解决命名冲突问题

变量后面加一个下划线。主要用于解决命名冲突问题,元编程中遇时Python保留的关键字时,需要临时创建一个变量的副本时,都可以使用这种机制。

def type_obj_class(name,class_):
    pass

def tag(name,*content,class_):
    pass

以上代码中出现的class是Python的保留关键字,直接使用会报错,使用下划线后缀的方式解决了这个问题。

三、_var用于保护变量

前面一个下划线,后面加上变量,这是仅供内部使用的“保护变量”。比如函数、方法或者属性。

这种保护不是强制规定,而是一种程序员的约定,解释器不做访问控制。一般来讲这些属性都作为实现细节而不需要调用者关心,随时都可能改变,我们编程时虽然能访问,但是不建议访问。

这种属性,只有在导入时,才能发挥保护作用。而且必须是from XXX import *这种导入形式才能发挥保护作用。

使用from XXX import *是一种通配导入(wildcard import),这是Python社区不推荐的方式,因为你根本搞不清你到底导入了什么属性、方法,很可能搞乱你自己的命名空间。PEP8推荐的导入方式是from XXX import aVar , b_func , c_func这种形式。

比如在下例汽车库函数tools.py里定义的“保护属性”:发动机型号和轮胎型号,这属于实现细节,没必要暴露给用户。当我们使用from tools import * 语句调用时,其实际并没有导入所有_开头的属性,只导入了普通drive方法。

_moto_type = 'L15b2'
_wheel_type = 'michelin'

def drive():
    _start_engine()
    _drive_wheel()

def _start_engine():
    print('start engine %s'%_moto_type)
    
def _drive_wheel():
    print('drive wheel %s'%_wheel_type)

查看命令空间print(vars())可见,只有drive函数被导入进来,其他下划线开头的“私有属性”都没有导入进来。

{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x005CF868>, '__spec__': None, '__annotations__':{}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '.\\xiahuaxian.py', '__cached__': None, 'walk': <function walk at 0x01DA8C40>, 'root': '.\\__pycache__', '_': [21536000, 16410.54], 'dirs': ['tools.cpython-38.pyc'], 'city': 'Beijing', 'code': '010', 'drive': <function drive at 0x01DBC4A8>}

3.1 突破保护属性

之所以说是“保护”并不是“私有”,是因为Python没有提供解释器机制来控制访问权限。我们依然可以访问这些属性:

import tools
tools._moto_type = 'EA211'
tools.drive()

以上代码,以越过“保护属性”。此外,还有两种方法能突破这个限制,一种是将“私有属性”添加到tool.py文件的__all__列表里,使from tools import *也导入这些本该隐藏的属性。

__all__ = ['drive','_moto_type','_wheel_type']

另一种是导入时指定“受保护属性”名。

from tools import drive,_start_engine
_start_engine()

甚至是,使用import tools也可以轻易突破保护限制。所以可见,“保护属性”是一种简单的隐藏机制,只有在from tools import *时,由解释器提供简单的保护,但是可以轻易突破。这种保护更多地依赖程序员的共识:不访问、修改“保护属性”。除此之外,有没有更安全的保护机制呢?有,就是下一部分讨论的私有变量。

四、__var用于私有变量

私有属性解决的之前的保护属性保护力度不够的问题。变量前面加上两个下划线,类里面作为属性名和方法都可以。两个下划线属性由Python的改写机制来实现对这个属性的保护。

看下面汽车例子中,品牌为普通属性,发动机为“保护属性”,车轮品牌为“私有属性”。

class Car:
    def __init__(self):
        self.brand = 'Honda'
        self._moto_type = 'L15B2'
        self.__wheel_type = 'michelin'

    def drive(self):
        print('Start the engine %s,drive the wheel %s,I get a running %s car'%
        (self._moto_type,
        self.__wheel_type,
        self.brand))

我们用var(car1)查看下具体属性值,

['_Car__wheel_type', '__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__', '_moto_type', 'brand', 'drive']

可见,实例化car1中,普通属性self.brand和保护属性self._moto_type都得以保存,两个下划线的私有属性__wheel_type没有了。取而代之的是_Car_wheel_type这个属性。这就是改写机制(Name mangling)。两个下划线的属性,被改写成带有类名前缀的变量,这样子类很难明明一个和如此复杂名字重名的属性。保证了属性不被重载,保证了其的私有性。

4.1 突破私有属性

这里“私有变量”的实现,是从解释器层面给与的改写,保护了私有变量。但是这个机制并非绝对安全,因为我们依然可以通过obj._ClasssName__private来访问__private私有属性。

car1.brand = 'Toyota'
car1._moto_type = '6AR-FSE'
car1._Car__wheel_type = 'BRIDGESTONE'
car1.drive()

结果

Start the engine 6AR-FSE,\
drive the wheel BRIDGESTONE,\
I get a running Toyota car

可见,对改写机制改写的私有变量,虽然保护性加强了,但依然可以访问并修改。只是这种修改,只是一种杂耍般的操作,并不可取。

五、__var__用于魔术方法

变量前面两个下划线,后面两个下划线。这是Python当中的魔术方法,一般是给系统程序调用的。例如上例中的__init__就是类的初始化魔术方法,还有支持len函数的__len__方法,支持上下文管理器协议的__enter__和__exit__方法,支持迭代器协议的__iter__方法,支持格式化显示的__repr__和__str__方法等等。这里我们为上例的Car类添加魔术方法__repr__来支持格式化显示。

    def __repr__(self):
        return '***Car %s:with %s Engine,%sWheel***'%
        (self.brand,self._moto_type,self.__wheel_type)

未添加__repr__魔术方法之前,print(car1)结果为<__main__.Car object at 0x0047F7F0>,这个结果让人看的一头雾水,增加repr魔术方法之后,显示结果为***Car Toyota:with 6AR-FSE Engine,BRIDGESTONE Wheel***清晰明了,利于调试。这就是魔术方法的功效:支持系统调用,改进用户类表现,增加协议支持,使用户类表现得更像系统类。

5.1 Python魔术方法分类

以下所有魔术方法均需要在前后加上__,这里省略了这些双下划线。

  • 一元运算符 neg pos abs invert
  • 转换 complex int float round inex
  • 算术运算 add sub mul truediv floordiv mod divmod pow lshift rshift and xor or

算术运算除and之外,前面再加上r,表示反运算。除dimod外,前面加上i,表示就地运算。

  • 比较 lt le eq ne gt ge
  • 类属性 getattr getattribute setattr delattr dir get set delete
  • 格式化 bytes hash bool format
  • 类相关 init del new
  • 列表 getitem
  • 迭代器 iter next
  • 上下文管理器 enter exit

六、总结

总之,下划线在 Python 当中应用还是很广泛的,甚至可以说 Python 对下划线有所偏爱

可以看到 _常用于临时变量,在REPL,for循环,元组拆包和国际化中得到了广泛应用

var_用于解决命名冲突问题,使用时比较简单易懂的。_var对变量的保护,只是一种脆弱的保护,更多依靠程序员的约定。__var用于私有变量,借助改写机制支持,已经支持了私有变量,但是仍然存在漏洞

对__var__用于魔术方法,进行了一个简单的介绍,魔术方法较多,但是理解并不复杂。希望以后可以进一步介绍这些魔术方法

以上就是python 下划线的多种应用场景总结的详细内容,更多关于python 下划线应用场景的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
python 中文乱码问题深入分析
Mar 13 Python
python迭代器的使用方法实例
Nov 21 Python
Python中实现两个字典(dict)合并的方法
Sep 23 Python
Python的Django框架中的表单处理示例
Jul 17 Python
Python 的内置字符串方法小结
Mar 15 Python
python批量制作雷达图的实现方法
Jul 26 Python
Php多进程实现代码
May 07 Python
Python3 requests文件下载 期间显示文件信息和下载进度代码实例
Aug 16 Python
解决Django连接db遇到的问题
Aug 29 Python
基于Python下载网络图片方法汇总代码实例
Jun 24 Python
pycharm2020.2 配置使用的方法详解
Sep 16 Python
python接口测试返回数据为字典取值方式
Feb 12 Python
超级详细实用的pycharm常用快捷键
pycharm 如何查看某一函数源码的快捷键
教你使用Pandas直接核算Excel中快递费用
用python开发一款操作MySQL的小工具
May 12 #Python
浅谈Python类的单继承相关知识
May 12 #Python
PyCharm 安装与使用配置教程(windows,mac通用)
在python中实现导入一个需要传参的模块
May 12 #Python
You might like
php处理json时中文问题的解决方法
2011/04/12 PHP
Warning: session_destroy() : Trying to destroy uninitialized sessionq错误
2011/06/16 PHP
php中mt_rand()随机数函数用法
2014/11/24 PHP
php array_merge函数使用需要注意的一个问题
2015/03/30 PHP
PHP代码实现爬虫记录――超管用
2015/07/31 PHP
Laravel框架基于中间件实现禁止未登录用户访问页面功能示例
2019/01/17 PHP
wordpress之js库集合研究介绍
2007/08/17 Javascript
js 实现无缝滚动 兼容IE和FF
2009/07/15 Javascript
jQuery EasyUI NumberBox(数字框)的用法
2010/07/08 Javascript
基于JavaScript实现继承机制之构造函数方法对象冒充的使用详解
2013/05/07 Javascript
复选框全选与全不选操作实现思路
2013/08/18 Javascript
jQuery简单动画变换效果实例分析
2016/07/04 Javascript
bootstrap监听滚动实现头部跟随滚动
2016/11/08 Javascript
vue+Java后端进行调试时解决跨域问题的方式
2017/10/19 Javascript
原生JS实现手动轮播图效果实例代码
2018/11/22 Javascript
微信小程序提取公用函数到util.js及使用方法示例
2019/01/10 Javascript
jquery.pager.js分页实现详解
2019/07/29 jQuery
maptalks+three.js+vue webpack实现二维地图上贴三维模型操作
2020/08/10 Javascript
[56:46]2018DOTA2亚洲邀请赛 3.31 小组赛 B组 VP vs Effect
2018/04/01 DOTA
举例讲解Python中的死锁、可重入锁和互斥锁
2015/11/05 Python
Python实现删除时保留特定文件夹和文件的示例
2018/04/27 Python
python实现决策树分类(2)
2018/08/30 Python
PyQt5实现五子棋游戏(人机对弈)
2020/03/24 Python
python实现一个简单的ping工具方法
2019/01/31 Python
Django uwsgi Nginx 的生产环境部署详解
2019/02/02 Python
keras自定义损失函数并且模型加载的写法介绍
2020/06/15 Python
python Yaml、Json、Dict之间的转化
2020/10/19 Python
css3实现波纹特效、H5实现动态波浪效果
2018/01/31 HTML / CSS
环境科学专业个人求职信
2013/12/15 职场文书
高中军训感言600字
2014/03/11 职场文书
绿色环保口号
2014/06/12 职场文书
教室标语大全
2014/06/21 职场文书
2015年调度员工作总结
2015/04/30 职场文书
关于迟到的检讨书
2015/05/06 职场文书
CSS SandBox应用场景及常见问题
2022/06/25 HTML / CSS
Python中的 No Module named ***问题及解决
2022/07/23 Python