Python编程规范笔记(上)
写在前面:
从C语言开始,自己陆续学习了C++/Java,但是自从研究生做毕设接触Python以来,就爱不释手,再也没有动力尝试其他语言。然而惭愧的是,由于一直从事科研工作,因为很少关注代码规范性与代码效率,导致自己之前论文所做实验代码可读性极差。故而忙里抽闲得一半日,粗揽《Python编程之美》,收货颇丰,故而重做于屏幕前,权当整理记录。由于全书于己涉及知识点不少,故而仅挑选当前急用者梳理之。
一、PEP_8与PEP_20
Python的一大优势就是具备优秀的可读性,而这基于一套较为完整的公认编程规范。这里建议所有初学或使用Python的同学,可以先从PEP_8入手,其中包括了命名、空格、换行以及函数参数等多个最常用最基本的编程规范。
以自己为例,通常命名变量都采取大小写组合的形式,如一个表示用户特征的变量,通常会命名为Users_Feats_lst,而阅读了PEP_8之后才发觉这样写十分不美观,导致读起来十分混乱,推荐的命名规范是:
- 常量可以采用全大写;
- 类名可以采用HelloWorld的单词首字母大写形式;
- 其余函数与变量名,全部推荐采用小写形式;
上面是自己初读完PEP_8立刻就用上的一点小规范,感觉很实用。因此建议大家在开始Python前抽出十分钟先读读PEP_8/20,后者是对于前者的一个扩充升级,不过我建议初学的同学先从8开始就可以了,逐步学习即可。
二、一般性建议归纳
这部分我们就开始梳理感觉最实用的规范了。
2.1 明确胜于隐晦
Python中提供了同一功能的多种编码实现方式,那么越明确越简练的方式就越推荐。判断代码写得是否优雅的一个经验法则是:其他开发者是否可以只阅读函数的首行与末行就理解函数的作用。
2.2 留白胜于紧凑
Python一般要求一行仅写一个语义句,避免多步处理写在同一行;当然,如果一个长参数函数可以在函数部分断行,如使用反斜杠**或者小括号。
2.3 尽量仅在一处返回函数结果
当遇到多情况的if-else判断时,往往可以在每个判断后返回结果,然而这与具有多重出口的迷宫一样,会降低可读性,故而一般要求函数写成仅有一个返回return的形式。
三、约定
3.1 检查相等性的替代方法
对于和逻辑变量以及0比较的情况,相等性检查全部建议替换为直接使用if语句,如下述情况就推荐2)的形式。
1)if flag_0 == True:
pass
2)if flag_0:
pass
3.2 操作列表(列表解析与filter/map函数)
一般而言循环操作列表时我们都喜欢使用以下方法,如从下述列表中筛选出大于等于4的元素:
a = range(10)
b = []
for ele in a:
if a > 4:
b.append(ele)
else:
continue
print b
然而,现在我们要推荐一种更为简洁优美的方法:列表解析
a = range(10)
b = [i + 2 for i in a if i > 4]
print b
-->[7, 8, 9, 10, 11]
即,我们只要遵循格式[ele operator | ele from where | ele condation]即可,进一步地,上述代码结果说明不仅可以设置筛选特定条件的元素,进一步还可以对每个元素进行运算。
当然,如果上面的公式你感觉别扭不容易读,那么也可以使用同样功能的函数,只是筛选对应filter(),运算对应map(),其中filter函数返回筛选条件为True的元素组成的新列表,而map函数返回函数操作结果组成的新列表。
c = filter(lambda x : x > 4, a)
print c
-->[5, 6, 7, 8, 9]
d = map(lambda x : x * x, a)
print d
-->0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
3.3 解包
解包操作主要用于已知列表或者元祖长度的情况下分配元素,其一般通用公式为:
taget_1, taget_2,...,target_N=长度N的列表或者元祖
需要注意的是,仅在Python 3.0以上版本中,才支持可变目标参数的解包(Unpack)
3.4 创建一个包含N个相同对象的列表
你可能会想这还不简单?但是其实很容易犯错,我们先给出总结的规律:
- 若非可变对象*N,可以得到正确的N个相同对象的列表
- 对于空对象[]*N,结果依旧是空对象[]
- 对于可变对象[[]]*N,其结果默认是N个指向同一个列表的对象列表
# case 1
four_nones = [None]
print four_nones * 4
-->[None, None, None, None]
# case 2
four_nones = []
print four_nones * 4
-->[]
# case 3
four_nones = [[]] * 4
print four_nones
-->[[], [], [], []]
four_nones[0].append(1) # still be list, so save the copy attr
print four_nones
-->[[1], [1], [1], [1]]
four_nones[0] = 2 # changed to be integer not list
print four_nones
-->[2, [1], [1], [1]]
# suggest format: 列表解析
four_nones = [[] for i in range(4)]
four_nones[0].append(100)
print four_nones
-->[[100], [], [], []]
四、常见陷阱
所谓陷阱,自然是容易犯错的部分,这里简单梳理两例。
4.1 可变的默认参数
有些时候我们希望定义一个可供调用的函数,其中包含一个可变参数以适应不同调用场景。如:
# wrong case:
def append_to(ele, to=[]):
to.append(ele)
return to
my_list_1 = append_to(10)
print my_list_1
-->[10]
my_list_2 = append_to(20)
print my_list_2
-->[10, 20]
细心的同学可能发现了,我们本希望两次函数调用分别得到[10]与[20],结果第二次执行时却保留了第一次执行的结果,也就是说,虽然我们使用了可变参数作为默认参数,导致其并未在后续调用时刷新。Python在函数定义而非调用时自动计算其默认参数。
一个解决的简单方法是,直接设置一个默认值表示没有提供默认参数,而是每次重新初始化对象:
def append_to(ele, to=None):
if to is None:
to=[]
to.append(ele)
return to
my_list_1 = append_to(10)
print my_list_1
-->[10]
my_list_2 = append_to(20)
print my_list_2
-->[20]
4.2 延迟绑定的闭包
有些时候,我们希望可以采用循环的方式创建多个唯一性函数,而这时候其中的循环变量带来的延迟绑定可能导致错误。如:
# wrong case:
def create_multipliers():
return [lambda x : x * i for i in range(5)]
for multiplier in create_multipliers():
print multiplier(2)
-->8
-->8
-->8
-->8
-->8
原本希望的结果是[0,2,4,6,8],结果却完全一样。其原因在于for循环依次访问函数对象列表时,肯定函数对象生成函数已经完成,返回的函数对象中i值全部为4,因此导致结果一致。此时只需要单独绑定一个默认参数i_0,避免受到i值影响即可:
def create_multipliers():
return [lambda x, i_0 = i : x * i_0 for i in range(5)]
for multiplier in create_multipliers():
print multiplier(2)
-->0
-->2
-->4
-->6
-->8
五、小结
今天所梳理的部分集中在两个方面:
- 列表解析带来的列表循环操作的优雅表达法以及创建N个相等对象列表时可能出现的浅复制问题,列表解析属于深复制;
- 函数调用过程中默认参数在定义时初始化而非调用时初始化,且循环创建唯一性函数时容易受到闭包绑定延迟带来的影响导致程序错误;
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。