编写高质量的Python代码系列(一)之用Pythonic方式来思考

2022-12-23,,,,

  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指南

    PEP 8 -- Style Guide for Python Code

    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块中。

编写高质量的Python代码系列(一)之用Pythonic方式来思考的相关教程结束。

《编写高质量的Python代码系列(一)之用Pythonic方式来思考.doc》

下载本文的Word格式文档,以方便收藏与打印。