Python在很多地方都用到了下划线。在不同的场合,有不同的含义:比如,_var表示内部变量;__var表示私有属性;__var__表示魔法方法;其中一些含义是程序员社区的约定,例如_var;有些是Python解释器指定的形式,例如__var。这篇文章总结了下划线在Python语言编程中常用的地方,尝试一下理解一下_的用法。目前常见的用法有五种:_fortemporaryvariablesvar_forresolvingnamingconflicts_varforprotectedvariables__varforprivatevariables__var__formagicmethods我们来详细了解下划线的这些应用场景。1._用于临时变量单下划线一般用于表示临时变量,多见于REPL、for循环、元组拆包等场景。1.1REPLREPL中的单个下划线与上次计算的非None结果相关联。>>>1+12>>>_2>>>a=2+2>>>_21+1,结果为2,赋值给_;而赋值表达式a=2+2a是4,但是整个表达式的结果是None,所以不会和_关联。这有点类似于大家每天使用的计算器中的ANS键,直接保存上次的计算结果。1.2for循环中的_for循环用作临时变量。下划线用于表示无意义的变量。比如下面的函数,当我们只关心函数执行的次数,而不关心具体的顺序时,可以使用_作为参数。nums=13for_inrange(nums):fun_oper()1.3_在元组拆包中的第三种用法是元组拆包。赋值时,可以用_表示跳过的内容。下面的代码忽略了北京的人口,只获取姓名和区号。>>>city,_,code=('Beijing',21536000,'010')>>>print(city,code)Beijing010如果需要跳过多个内容,可以*开头参数,表示忽略多个内容。下面代码忽略面积和人口,只获取名称和区号城市,*_,code=('北京',21536000,16410.54,'010')1.4国际化函数在一些国际化程序中,_常用来表示翻译函数名。例如使用gettext包时:importgettextzh=gettext.tranlation('dict','locale',languages=['zh_CN'])zh.install()_('helloworld')根据设置的字典文件,它返回对应的汉字“Helloworld”。1.5大数表示_也可以用来划分数字,一般在数字比较长的时候使用。>>>a=9_999_999_999>>>a9999999999a自动忽略下划线。这样用_来划分数字有利于比较大的数字方便读取。其次,var_用来解决命名冲突的问题。在变量后添加下划线。主要用来解决命名冲突的问题。当在元编程中遇到Python保留的关键字,需要临时创建一个变量的副本时,可以使用这种机制。deftype_obj_class(name,class_):passdeftag(name,*content,class_):pass上面代码中出现的class是Python的保留关键字,直接使用会报错,下划线后缀就是用来解决这个问题的。3._var用于保护变量,前面加下划线,后面跟变量。这是一个仅供内部使用的“受保护变量”。例如函数、方法或属性。这种保护不是强制的,而是程序员的约定,解释器不做访问控制。一般来说,这些属性作为实现细节使用,不需要调用者关心,随时可能发生变化。虽然我们在编程的时候可以访问到它们,但是不建议去访问它们。该属性只有在导入时才能起到保护作用。而且必须是fromXXXimport*这种导入形式可以起到保护作用。使用fromXXXimport*是一种通配符导入(wildcardimport),Python社区不推荐这种方式,因为你搞不清楚自己导入了哪些属性和方法,很可能会把自己的命名空间搞乱.PEP8推荐的导入方式是fromXXXimportaVar,b_func,c_func这种形式。例如下例中汽车库函数tools.py中定义的“保护属性”:enginemodel和tiremodel,这些是实现细节,不需要暴露给用户。当我们使用fromtoolsimport*语句调用时,实际上并没有导入所有以_开头的属性,而只是普通的驱动方法。_moto_type='L15b2'_wheel_type='michelin'defdrive():_start_engine()_drive_wheel()def_start_engine():print('启动引擎%s'%_moto_type)def_drive_wheel():print('驱动轮%s'%_wheel_type)查看命令空格print(vars()),可以看出只导入了驱动函数,其他下划线开头的“私有属性”没有导入。{'__name__':'__main__','__doc__':无,'__package__':无,'__loader__':<_frozen_importlib_external.SourceFileLoaderobjectat0x005CF868>,'__spec__':None,'__annotations__':{},'__builtins__':,'__file__':'.\\xiahuaxian.py','__cached__':None,'walk':,'root':'.\\__pycache__','_':[21536000,16410.54],'dirs':['tools.cpython-38.pyc'],'city':'北京','code':'010','drive':}3.1突破性保护属性之所以被“protected”并不是“private”,是因为Python没有提供解释器机制来控制访问权限。我们仍然可以访问这些属性:importtoolstools._moto_type='EA211'tools.drive()上面的代码绕过“受保护的属性”。此外,还有两种方法可以突破这个限制。一种是在tool.py文件的__all__列表中加入“私有属性”,这样fromtoolsimport*也导入了这些应该隐藏的属性。__all__=['drive','_moto_type','_wheel_type']另一个是导入时指定“受保护属性”的名称。fromtoolsimportdrive,_start_engine_start_engine()甚至,使用import工具都可以轻松突破保护限制。由此可见,“保护属性”是一种简单的隐藏机制。只有当fromtoolsimport*时,解释器提供了简单的保护,但很容易被攻破。这种保护更多地依赖于程序员的共识:不访问或修改“保护属性”。另外,有没有更安全的保护机制?是的,下一节将讨论私有变量。4.__var用于私有变量和私有属性,解决之前保护属性保护不足的问题。在变量前加两个下划线,可以作为类中的属性名或方法。这两个下划线属性受Python的重写机制保护。看下面的汽车例子,品牌是公共属性,发动机是“保护属性”,轮毂品牌是“私有属性”。classCar:def__init__(self):self.brand='Honda'self._moto_type='L15B2'self.__wheel_type='michelin'defdrive(self):print('启动引擎%s,驱动车轮%s,Igetarunning%scar'%(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。这就是重写机制(Namemangling)。带有两个下划线的属性被重写为带有类名前缀的变量,使得子类很难清楚地识别与这种复杂名称同名的属性。它确保财产不超载并保证其隐私。4.1突破私有属性这里的“私有变量”的实现是从解释器层面进行的改写,对私有变量进行了保护。但是这种机制并不是绝对安全的,因为我们仍然可以通过obj._ClasssName__private来访问__private私有属性。car1.brand='Toyota'car1._moto_type='6AR-FSE'car1._Car__wheel_type='BRIDGESTONE'car1.drive()结果启动引擎6AR-FSE,\驱动车轮BRIDGESTONE,\我看到一辆正在行驶的丰田汽车,虽然加强了对重写机制重写的私有变量的保护,但仍然可以访问和修改。只是这种修改只是一种杂耍操作,不可取。5.__var__用于魔术方法变量前面的两个下划线和后面的两个下划线。这是Python中的一个魔术方法,通常被系统程序调用。比如上例中的__init__是类的初始化魔术方法,还有支持len函数的__len__方法,支持上下文管理器协议的__enter__和__exit__方法,支持iterator协议的__iter__方法,__repr__和__str__等支持格式化显示的方法。这里我们在上例中的Car类中添加魔术方法__repr__来支持格式化显示。def__repr__(self):return'***Car%s:with%sEngine,%sWheel***'%(self.brand,self._moto_type,self.__wheel_type)在添加__repr__魔术方法之前,打印结果(car1)的是<__main__.Carobjectat0x0047F7F0>。这个结果令人困惑。添加repr魔术方法后,显示结果为CarToyota:with6AR-FSEEngine,BRIDGESTONEWheel,清晰易调试。这就是魔术方法的作用:支持系统调用,提高用户类性能,添加协议支持,并使用户类表现得更像系统类。5.1Python魔术方法的分类以下所有魔术方法的前后都需要加上__,这里省略了这些双下划线。一元运算符negposabs反转转换complexintfloatroundinex算术运算addsubmultruedivfloordivmoddivmodpowlshiftrshift和xoror算术运算除and外,在前面加r表示逆运算。除了dimod,前面加i表示就地操作。比较ltleeqnegtgeclassattributegetattrgetattributesetattrdelattrdirgetsetdeleteformatbyteshashboolformatclassrelatedinitdelnewlistgetitemiteratoriternextcontextmanagerenterexit非常广泛,甚至可以说Python有对下划线的偏好。可以看到_常用于临时变量,在REPL、for循环、元组拆包和国际化中被广泛使用。var_用于解决命名冲突。使用比较简单易懂。_var对变量的保护只是一种脆弱的保护,更多的依赖于程序员的约定。__var用于私有变量。在重写机制的支持下,私有变量已经支持了,但是还是有漏洞。__var__用于魔术方法。作了简要介绍。魔术方法很多,但理解并不复杂。希望以后能进一步介绍这些魔术方法。最近整理了数百G的Python学习资料,包括初学者电子书、教程、源码等,免费分享给大家!想上“Python编程学习圈”,发“J”免费领取