python 下划线的多种应用场景总结
作者:巩庆奎 发布时间:2021-12-19 10:51:54
目录
一、_用于临时变量
1.1 REPL
1.2 for循环中的_
1.3 元组拆包中的_
1.4 国际化函数
1.5 大数字表示形式
二、var_用于解决命名冲突问题
三、_var用于保护变量
3.1 突破保护属性
四、__var用于私有变量
4.1 突破私有属性
五、__var__用于魔术方法
5.1 Python魔术方法分类
六、总结
目前常见的用法有五种:
_用于临时变量
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__用于魔术方法,进行了一个简单的介绍,魔术方法较多,但是理解并不复杂。希望以后可以进一步介绍这些魔术方法
来源:https://mp.weixin.qq.com/s/Wjo63pNOd3S0AIbK57L8qA
猜你喜欢
- 学习使用存储过程(Stored Procedure),是ASP程序员的必须课之一。所有的大型数据库都支持存储过程,比如Oracle
- 今天网页调试的时候在线订单出现错误:Server 对象 错误 'ASP 0178
- 本文实例讲述了Yii开启片段缓存的方法。分享给大家供大家参考,具体如下:1、main.php文件中的components中添加:cache&
- 数据库镜像方案有两种镜像运行模式。一种是“高安全性模式”,它支持同步操作。在高安全性模式下,当会话开
- PyQt5图片显示控件QPixmap介绍QPixmap类用于绘图设备的图像显示,它可以作为一个QPainterDevice对象,也可以加载到
- 使用router.meta.keepAlive对页面进行缓存需求:1. 从stockList页面到stockInfo页面,从stockInf
- <%sql = "select * from SMT_addt
- 先看看结果吧,去bilibili上拿到的图片=-=第一步,导入模块import requestsfrom bs4 import Beauti
- 单神经元引论对于如花,大美,小明三个因素是如何影响小强这个因素的。这里用到的是多元的线性回归,比较基础from numpy import a
- 1 :普通SQL语句可以用exec执行Select * from tableName exec('select * from tab
- 部署环境: jdk1.7mysql5.6tomcat7centos6.51资料准备 1)linux服务器,推荐使用阿里云,这里预算有限,所以
- Vuexvuex执行过程🎈相当于一个公共的资源库,保存共有的数据🎈使用场景:点击按钮后,将数据保存到store身上,跳转路由后使用🎈将act
- 源代码、参数及其意义:def sub(pattern, repl, string, count=0, flags=0): &n
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN&
- 1、路径https://www.lfd.uci.edu/~gohlke/pythonlibs/PS:网上说有时候报404,解决办法是换浏览器
- 前言临近期末做了一个商品销售管理系统,分享下,全部源码在码云,需要自取,博客只分享部分代码(太多了)。数据库的建立我们使用Navicat P
- 国庆假期快到了,想查查还有几天几小时到假期,这对程序员
- 微信扫码支付分为两种模式,模式一比较复杂,需要公众号配置回调地址。模式二比较简单,只需要在代码中配置回调地址就可以了。我这次使用的是模式二。
- 代码如下#!/bin/python#coding=utf-8#python-version=2.75  
- 一 前言知识追寻者又要放大招了,学完这篇openpyxl第三方库,读者将会懂得如何灵活的读取excel数据,如何创建excel工作表;更新工