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

      标签:python,下划线
      0
      投稿

      猜你喜欢

    • 如何在Unix系统环境下安装MySQL数据库

      2009-01-04 13:09:00
    • python处理大数字的方法

      2022-06-09 02:19:03
    • pandas获取groupby分组里最大值所在的行方法

      2021-08-14 21:39:14
    • 详解python itertools功能

      2022-12-05 23:07:31
    • 探讨链接打开方式

      2009-03-16 16:55:00
    • asp中判断是否是手机浏览器以及手机类型

      2014-12-06 09:33:05
    • 下载golang.org/x包的操作方法

      2023-07-11 16:54:04
    • python三大器之迭代器、生成器、装饰器

      2022-04-14 13:44:20
    • python中playwright结合pytest执行用例的实现

      2022-12-13 14:28:58
    • 服务端XMLHTTP(ServerXMLHTTP in ASP)基本应用(上)

      2008-11-11 12:49:00
    • Javascript 闭包[翻译]

      2008-09-28 20:59:00
    • 使用python 打开文件并做匹配处理的实例

      2022-01-15 16:27:30
    • 你的网站使用了微格式了么

      2009-05-21 12:10:00
    • Perl下应当如何连接Access数据库

      2008-12-04 13:06:00
    • python 阿里云oss实现直传签名与回调验证的示例方法

      2021-12-08 00:30:18
    • 盘点十个超级好用的高级Python脚本

      2023-12-04 23:41:27
    • Python实现获取域名所用服务器的真实IP

      2022-08-13 20:10:00
    • 页面重构中的模块化思维

      2009-06-28 15:36:00
    • python 利用turtle库绘制笑脸和哭脸的例子

      2022-01-16 08:35:30
    • Python读取键盘输入的2种方法

      2023-03-14 22:08:37
    • asp之家 网络编程 m.aspxhome.com