编写高质量的Python代码系列(一)之用Pythonic方式来思考
Python开发者用Pythonic这个形容词来描述具有特定风格的代码。这种风格是大家在使用Python语言进行编程并相互协作的过程中逐渐形成的习惯。那么,如何以改风格完成常见的Python编程工作呢?本节将会回答这个问题。
-
第一条:确认自己所用的Python版本
-
第二条:遵循PEP8风格指南
-
第三条:了解bytes、str与unicode的区别
-
第四条:用辅助函数来取代复杂的表达式
-
第五条:了解切割序列的方法
-
第六条:在单次切片操作内,不要同时指定start、end和stride
-
第七条:用列表推导来取代map和filter
-
第八条:不要使用含有两个以上表达式的列表推导
-
第九条:用生成器表达式来改写数据量较大的列表推导
-
第十条:尽量用enumerate取代range
-
第十一条:用zip函数同时遍历两个迭代器
-
第十二条:不要在for和while循环后面写else块
-
第十三条:合理利用try/exceot/else/finally结构中的每个代码块
第一条:确认自己所用的Python版本
C:\Users\fei\Desktop>python --version Python 3.6.5 import sys sys.version Out[14]: '3.6.5 (v3.6.5:f59c0932b4, Mar 28 2018, 17:00:18) [MSC v.1900 64 bit (AMD64)]'
要点
-
要点1:有两个版本的Python处于活跃状态,它们是:Python2和Python3
-
要点2:有很多中流行的Python运行时环境,例如: Cpython, Jython,IronPython以及PyPy等
-
要点3:在操作系统的命令行中运行Python时,请确保该Python的版本与你想使用的Python版本相符。
-
要点4:由于Python社区把开发重点放在Python3上,所以在开发后序项目时,应该优先考虑采用Python3
第二条:遵循PEP8风格指南
《Python Enhancement Proposal #8》(8号Python增强提案)又叫PEP8,它是针对Python代码格式而编订的风格指南。尽管可以在保证语法正确的前提下随意编写Python代码,但是采用一致的风格书写代码可以令代码更加易懂、更加易读。
完整版PEP8指南
下面列出几条绝对应该遵守的规则
空白: Python中的空白( whitespace)会影响代码的含义。 Python程序员使用空白 的时候尤其在意,因为它们还会影响代码的清晰程度。
-
使用 space(空格)来表示缩进,而不要用tab(制表符)。
-
和语法相关的每一层缩进都用4个空格来表示。
-
每行的字符数不应超过79。
-
对于占据多行的长表达式来说,除了首行之外的其余各行都应该在通常的缩进 级别之上再加4个空格。
-
文件中的函数与类之间应该用两个空行隔开。
-
在同一个类中,各方法之间应该用一个空行隔开。
-
在使用下标来获取列表元素、调用函数或给关键字参数赋值的时候,不要在两 旁添加空格。
-
为变量赋值的时候,赋值符号的左侧和右侧应该各自写上一个空格,而且只写 个就好。
命名:PEP8提倡采用不同的命名风格来编写 Python代码中的各个部分,以便在阅 读代码时可以根据这些名称看出它们在 Python语言中的角色。
-
函数、变量及属性应该用小写字母来拼写,各单词之间以下划线相连,例如lowercase_underscore。
-
受保护的实例属性,应该以单个下划线开头,例如, leading underscore
-
私有的实例属性,应该以两个下划线开头,例如, double leading underscore
-
类与异常,应该以每个单词首字母均大写的形式来命名,例如, Capitalized Word 口模块级别的常量,应该全部采用大写字母来拼写,各单词之间以下划线相连, 例如, ALL CAPS。
-
类中的实例方法( instance method)。应该把首个参数命名为sef,以表示该对象自身
-
类方法( lass method)的首个参数,应该命名为cbs,以表示该类自身。
表达式和语句:( The Zen of Python)( Python之禅)中说:“每件事都应该有直白的做 法,而且最好只有一种。”PEP8在制定表达式和语句的风格时,就试着体现了这种思想。
-
采用内联形式的否定词,而不要把否定词放在整个表达式的前面,例如,应该 写 if a is not b而不是 if not a is b。
-
不要通过检测长度的办法(如 if len(somelist)=0)来判断 somalis是否为目 或”等空值,而是应该采用 if not somelist这种写法来判断,它会假定:空值将 自动评估为 False
-
检测 somalis是否为]或hi等非空值时,也应如此, if somelist语句默认会把 非空的值判断为Tme。
-
不要编写单行的i语句、for循环、 while循环及cxp复合语句,面是应该把 这些语句分成多行来书写,以示清晰。
-
import语句应该总是放在文件开头。
-
引入模块的时候,总是应该使用绝对名称,而不应该根据当前模块的路径来 使用相对名称。例如,引入bar包中的foo模块时,应该完整地写出 from bar import foo,面不应该简写为 import foo。
-
如果一定要以相对名称来编写ipon语句,那就采用明确的写法: from import food 口文件中的那些mpon语句应该按顺序划分成三个部分,分别表示标准库模块 第三方模块以及自用模块:在每一部分之中,各如m语句应该按模块的字母 顺序来排列。
要点
-
要点1:当编写Python代码时,总是应该遵循PEP8风格指南。
-
要点2:与广大Python开发者采用同一套代码风格,可以使项目更利于多人协作。
-
要点3: 采用一致的风格来编写代码,可以令后续的修改工作变得更为容易。
第三条:了解bytes、str与unicode的区别
Python3有两种表示字符序列的类型:bytes和str。前者的实例包含原始的8位值;后者的实例包含Unicide字符。
Python2也有两种表示字符序列的类型,分别叫做str和unicode。与Python3不同的是,str的实例包含原始的8位值;而unicode的实例,则包含Unicode字符。
注:unicode --> 二进制 encode方法
二进制 --> unicode decode方法
要点:
-
要点1:在Python3中,bytes是一种包含8位值的序列,str是一种包含unicoe字符的序列。开发者不能以>或+等操作符来混同操作bytes和str实例
-
要点2:在Python2中,str是一种包含8位值的序列,unicode是一种包含Unicode字符的序列。如果str只包含有7位ASCII字符,那么可以通过相关额操作符同时使用str与unicode。
-
要点3:在对输入的数据进行操作之前,使用辅助函数来保证字符序列的类型与开发者的期望相符(有的时候,开发者想操作以UTF-8格式来编码的8位值,有的时候,则想操作Unicode字符)。
-
要点4:从文件中读取二进制数据,或向其中写入二进制数据时,总应该以‘rb'或‘wb’等二进制模式来开启文件。
第四条:用辅助函数来取代复杂的表达式
要点:
-
要点1:开发者很容易过度运用Python的语法特性,从而写出那种特别复杂并且难以理解的单行表达式
-
要点2:请把复杂的表达式移入辅助函数之中,如果要反复使用相同的逻辑,那就更应该这样做
-
要点3:使用if/else表达式,要比用or或and这样的Boolean操作符写成的表达式更加清晰。
第五条:了解切割序列的方法
要点:
-
要点1:不要写多余的代码:当start索引为0,或end索引为序列长度时,应该将其省略。
-
要点2:切片操作不会计较start与end索引是否越界,这使得我们很容易就能从序列的前端或后端开始,对其进行范围固定的切片操作(如a[:20]或a[-20:])。
-
要点3:对list赋值的时候,如果使用切片操作,就会把原列表处在相关范围内的值替换成新值,即使他们的长度不同也依然可以替换。
- 要点4:如果对赋值操作右侧的列表使用切片,而把切片的起止索引都留空,那就会产生一份原列表的拷贝。
-
要点5:如果对赋值左侧的列表使用切片,而又没有指定起止索引,那么系统会把右侧的新值复制一份,并好用这份拷贝替换左侧列表的全部内容,而不会重新分配新的列表。
示例代码
a = [1,2,3,4,5] b = a id(a) Out[4]: 588602575880 id(b) Out[5]: 588602575880 c = a[:] id(c) Out[7]: 588602487112 a Out[8]: [1, 2, 3, 4, 5] id(a) Out[9]: 588602575880 a[2:4] = [10,11] id(a) Out[11]: 588602575880 a Out[12]: [1, 2, 10, 11, 5] a[2:4] = [2,2,2,2,2] a Out[14]: [1, 2, 2, 2, 2, 2, 2, 5] id(a) Out[15]: 588602575880
第六条:在单次切片操作内,不要同时指定start、end和stride
要点
-
要点1:既有start和end,又有stride的切割操作,可能会令人费解。
-
要点2:尽量使用stride为整数,切不带start或end索引的切割操作。尽量避免用负数做sride.
-
要点3:再同一个切片操作内,不要同时使用start、end和stride。如果确实需要执行这种操作,那就考虑将其拆解为两条赋值语句,其中一角做范围切割,另一条做步进切割,或考虑使用内置函数itertools模块中的islice。
第七条:用列表推导来取代map和filter
-
要点1: 列表推导比内置的map和filter函数清晰, 因为它无需额外编写lanbda表达式
-
要点2: 列表推导可以跳过输入列表中的某些元素,如果改用map来做,那就必须辅以filter方能实现
-
要点3:字典和集合也支持列表推导
第八条:不要使用含有两个以上表达式的列表推导
-
要点1: 列表推导支持多级循环,每一级循环也支持多项条件
-
要点2: 超过两个表达式的列表推导式很难理解的,应该尽量避免
第九条:用生成器表达式来改写数据量较大的列表推导
-
要点1: 当输入数据量较大时,列表推导可能会因为占用太多内存而出问题
-
要点2: 由生成器表达式所返回的迭代器,可以逐次产生输出值,从而避免了内存用量问题。
-
要点3: 把某个生成器表达式所返回的迭代器,放在另一个生成器表达式的for子表达式中,即可将二者组合起来
-
要点4: 串在一起的生成器表达式执行速度很快。
第十条:尽量用enumerate取代range
-
要点1:enumerate函数提供了一种精简的写法,可以在遍历迭代器时获知每个元素的索引
-
要点2: 尽量用enumerate来改写那种将range与下标访问相结合的序列遍历代码
-
要点3: 可以给enumerate提供第二个参数,已制定开始技术是所用的值(默认为0)
第十一条:用zip函数同时遍历两个迭代器
-
要点1: 内置的zip函数可以平行的遍历多个迭代器
-
要点2: Python3中的zip相当于生成器,会在遍历过程中逐次产生元组,而Python2中的zip则是直接把元组完全生成好,并一次性返回整份列表。
-
要点3: 如果提供的迭代器长度不等,那么zip就会自动提前终止
-
要点4:itertools内置模块中的zip_longest函数可以平行地遍历多个迭代器,而不用在乎它们的长度是否相等
第十二条:不要在for和while循环后面写else块
-
要点1: Python有种也属于语法,可在for及while循环的内部语句块之后紧跟一个else块
-
要点2: 只有当整个循环主体都没遇到break语句时,循环后面的else块才会执行
-
要点3: 不要在循环后面使用else块,因为这种写法既不直观,又容易引起误解
第十三条:合理利用try/exceot/else/finally结构中的每个代码块
-
要点1: 无论try块是否发生异常,都可利用try/finally复合语句中的finally块来执行清理工作
-
要点2: else块可以用来缩减try块中的代码量,并把没有发生异常时所要执行的语句与try/except代码块隔开
-
要点3: 顺利运行try块后,若想使某些操作能在finally块的清理代码之前执行,则可将这些操作写到else块中。