每一门编程语言都应该有编码规范,规范是一种良好的习惯,也是团队开发中重要的基础。
Python 编码规范的重要性,其实可用一句话来概括:统一的编码规范可以提高开发效率。而影响开发效率的大致有三个方面,即阅读者、编码者、机器,下面先从这三个角度来总结下编码规范的重要性。
作为一个开发者应该深有体会,实际工作中撸代码的时间并不多,更多时间是在阅读代码,因此,优化代码的阅读体验远比写代码更有意义。比如,代码写上简短而清晰的注释肯定比没有任何注释感觉要好吧,阅读者想要知道你模块或函数的功能,简明而清晰的命名比胡乱命名更让人感觉舒服。
从代码阅读者角度看,对那些命名规范,注释明了,逻辑清晰的代码,肯定会让人感到由衷的敬佩,这也算是开发者的基本素质了。而对于那些命名隐晦,也没有任何注释说明,逻辑看上去不知所云的,阅读起来既感到费劲又容易想让人产生口吐芬芳的冲动,经历过的应该都懂的!
Python 编程似乎存在一种很怪异的现象,那就是编码者总喜欢追求极简的代码风格。能将五六行代码写成一行的固然令人敬佩,但有没有想过:程序出现异常的话,会增加问题排查的难度;有没有想过别人阅读你的代码,因看不懂而降低阅读体验。因此,一行代码实现功能也是有利有弊的,我们不能只追求简洁或者炫技而忽略了其他的东西。
举个例子就明白了,像这种:
list = [(m, n) for m in range(10) for n in range(5) if m * n > 10]
乍一看,心里有一万个问号路过......!这种极简的代码风格对新手很不友好,估计要半天才明白这样写所表达的意思。个人建议尽量别这样写,拆开写成双重循环加if判断会显得更有章法,更能提升代码阅读体验。
无论是五六行行的编码,还是一行实现的极简代码,从机器的角度看,最终在解释器内都会被一样的执行。但是呢,有时候也需要考虑机器执行的效率,比如:不同场景下选择使用 is 还是选择使用 == ;数据量较多的列表中是否使用生成器等。
编程终极目标的是提高效率,编码者是很关键的一环,作为有追求的开发者,有必要思索自己的编程行为,有几点建议仅供参考:
因此,编码过程中养成遵守编程规范的意识,不仅可以写出阅读体验良好的代码,也能写出在机器上具有健壮性和可移植性的代码,何乐而不为呢?!
万丈高楼平地起,基础规范也至关重要,比如:命名规范、良好的书写风格、注释规范、导包规范、字符串规范、函数规范等。
变量名是由英文字母、数字、下划线组成,且不能以数字开头,变量命名时要科学严谨,切勿太长,切勿表达含糊,常见的规范要求有:
命名参考:
拥有良好的代码书写风格,是每个编码者素质的直接体现,常见的约定有:
关于注释规范,比如,单行注释:可以用#,也可以用''' ''','''''' '''''' ;多行注释:用''' ''','''''' '''''',也可以用多个# 。注释要遵守 pydoc 规范:
建议使用专业的编辑器书写,写完后美化代码,尽量避免低级错误。
关于字符串规范,禁止单引号和双引号混用,避免影响代码的可读性,目前字符串格式化支持 %、format()、f-string 等风格。使用字符串需注意:
# 建议
print('欢迎%s,%s,来到杭州' % (name1, name2))
print('欢迎{},{},来到杭州'.format(name1, name2))
# 不建议
print('欢迎%s%s,来到杭州' % (name1, name2))
print('欢迎{}{},来到杭州'.format(name1, name2))
print('欢迎%s,%s,来到杭州' % (name1, name2))
print('欢迎{},{},来到杭州'.format(name1, name2))
# f-string看起来更简单明了
print(f'欢迎{name1},{name2},来到杭州')
正确做法是,循环时将字符串放入列表中,循环结束后使用.join()方法连接列表中字符串即可。
# \(在行尾时),续行符
print("line1 \
... line2 \
... line3")
# \'表示单引号,\"表示双引号,\\表示反斜杠符号
print('\'', "\"", "\\")
# \a表示响铃(执行后电脑有响声),\b表示退格(Backspace),\000表示空,\n表示换行
print("\a", "\b", "\000", "\n")
# \f表示换页,\v表示纵向制表符,\t表示横向制表符
print("Hello \f World!", "Hello \v World!", "Hello \t World!")
如果需要输出带转义字符的字符串,前面加上r 或 R 即可,例如:
# 使用正则表达式判断是否为对象或图片
text = pdf.xref_object(i)
isXObject = re.search(r"/Type(?= */XObject)", text)
isImage = re.search(r"/Subtype(?= */Image)", text)
在避免代码重复和冗余时,经常会用到函数,精心设计的函数不仅可以提高程序的健壮性,还可以增强可读性、减少维护成本。函数设计需要考虑的注意事项有:
<1>、常量管理:
对字符串常量、数字常量、异常码等建议统一命名,最好集中放置管理,便于后续的使用和维护。
<2>、是==还是is:
在 Java 中我们区分的是==与 equals() 的区别,相信每个开发都能从内存空间的角度对答如流。因为 Python 也是一门面向对象的语言,区分==与 is 也是从对象的内存管理角度分析的,==比较的是两个对象的值是否相等,而 is 比较的是两个对象在内存空间的地址是否一致,哈哈,举一反三,这下就记得住了~
<3>、交换变量不需要借助中间变量:
记得 Java 编码实现两个变量位置的交换,通常的做法是借助一个中间变量 temp 完成,而 Python 的话,直接通过 a, b = b, a 这种表达式即可实现,而且性能比使用中间变量更佳,测试下:
from timeit import Timer
value = 'a=5; b=6'
exp1 = 'temp=a; a=b; b=temp'
exp2 = 'a,b = b,a'
# 使用中间变量耗时:0.020298399999999994
print(Timer(exp1, value).timeit())
# 不使用中间变量耗时:0.01742450000000001
print(Timer(exp2, value).timeit())
<4>、None 的问题:
Python 的常量 None 其实是一个空值对象。它既不是0,也不是 False,更不是空串"",自然也不是空元组()、空列表[]、空字典{} 等,可以简单测试下:
print(None == 0) # False
print(None == False) # False
print(None == "") # False
print(None == ()) # False
print(None == []) # False
print(None == {}) # False
print(None == None) # True
print(type(None)) #
使用 None 时需要注意它的特殊性,其类型为 NoneType,用来判断对象是否为空的。
<5>、函数 str() 和 repr() 的区分:
二者都可以将 Python 中的对象转换为字符串,并且使用和输出都非常相似。对于不同类型的输入,这两个函数的输出有何异同,比较如下:
repr() 和 str() 对于大多数数据类型的输出基本一致,因此很容易混淆。二者有以下几点明显差异:
<6>、字符串连接:
Python 对字符串连接操作有两种方式:+、join()。使用+连接符操作的话,在内存中就是每次连接都要申请一块新的内存空间,整体上时间复杂度为 O(n^2);而使用 join(),则先计算需要申请的总内存,然后一次性申请所需内存并字符序列的每一个元素copy到内存中去,整体上的时间复杂度为 O(n)。因此,进行字符串连接操作时应该优先使用 join() 函数!!!
<7>、使用 with 语句自动关闭资源连接:
在操作文件、数据库等对象之后,需要手动关闭这些资源的连接,初学 Python 肯定使用过 try-except-finally 语句块捕捉异常并关闭资源,这样看起来代码非常臃肿。Python 提供了 with 关键字来简化这一情况,使用 with 语句不用再去显式的关闭资源连接。对比下二者的区别:
# try-except-finally 语句块
try:
f = open('1.txt','r',encoding='utf-8')
f.read()
except Exception as e:
raise e
finally:
f.close()
# with 语句,两行代码搞定!!!
with open('1.txt','r',encoding='utf-8') as f:
f.read()
另外,with 语句还可以嵌套多个 with 语句,如下:
with open('1.txt','r',encoding='utf-8') as f1:
with open('2.txt', 'r', encoding='utf-8') as f2:
with open('3.txt', 'r', encoding='utf-8') as f3:
with open('4.txt', 'r', encoding='utf-8') as f4:
pass
<8>、惰性计算:
我们知道,在 if 条件表达式中会存在一种短路运算机制,比如,if a and b,当 a 为 True 时,无论 b 为何值都不会去计算 b,这样可以避免不必要的计算,带来性能上的提升。
Python 还有一个强大的惰性计算表达式,即生成器表达式,原理是仅仅在每次需要计算的时候,才通过 yield 关键字产生所需要的元素。试想一下,一个列表内放入了10亿条数据,通过循环操作时每次都要遍历10亿次,这种操作明显不可取!
测试一下死循环场景(斐波那契数列),假如在死循环里计算变量s扩大2倍的结果,当使用 yield 返回s,则会在计算该次运算后跳出循环,如下:
from itertools import islice
def count():
s = 1
while True:
yield s
s = s * 2
if __name__ == '__main__':
print(list(islice(count(), 10)))
# 测试结果:[1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
Python 编码规范内容还有很多,这篇无法面面俱到,后面有时间将继续记录和分享。