【记录】非常实用,Python编码规范的一些建议(1)

每一门编程语言都应该有编码规范,规范是一种良好的习惯,也是团队开发中重要的基础。

一、认识编码规范的重要性

Python 编码规范的重要性,其实可用一句话来概括:统一的编码规范可以提高开发效率。而影响开发效率的大致有三个方面,即阅读者、编码者、机器,下面先从这三个角度来总结下编码规范的重要性。

阅读者

作为一个开发者应该深有体会,实际工作中撸代码的时间并不多,更多时间是在阅读代码,因此,优化代码的阅读体验远比写代码更有意义。比如,代码写上简短而清晰的注释肯定比没有任何注释感觉要好吧,阅读者想要知道你模块或函数的功能,简明而清晰的命名比胡乱命名更让人感觉舒服。

从代码阅读者角度看,对那些命名规范,注释明了,逻辑清晰的代码,肯定会让人感到由衷的敬佩,这也算是开发者的基本素质了。而对于那些命名隐晦,也没有任何注释说明,逻辑看上去不知所云的,阅读起来既感到费劲又容易想让人产生口吐芬芳的冲动,经历过的应该都懂的!

编码者

Python 编程似乎存在一种很怪异的现象,那就是编码者总喜欢追求极简的代码风格。能将五六行代码写成一行的固然令人敬佩,但有没有想过:程序出现异常的话,会增加问题排查的难度;有没有想过别人阅读你的代码,因看不懂而降低阅读体验。因此,一行代码实现功能也是有利有弊的,我们不能只追求简洁或者炫技而忽略了其他的东西。

举个例子就明白了,像这种:

list = [(m, n) for m in range(10) for n in range(5) if m * n > 10]

乍一看,心里有一万个问号路过......!这种极简的代码风格对新手很不友好,估计要半天才明白这样写所表达的意思。个人建议尽量别这样写,拆开写成双重循环加if判断会显得更有章法,更能提升代码阅读体验。

机器

无论是五六行行的编码,还是一行实现的极简代码,从机器的角度看,最终在解释器内都会被一样的执行。但是呢,有时候也需要考虑机器执行的效率,比如:不同场景下选择使用 is 还是选择使用 == ;数据量较多的列表中是否使用生成器等。

关键还是编码者!

编程终极目标的是提高效率,编码者是很关键的一环,作为有追求的开发者,有必要思索自己的编程行为,有几点建议仅供参考:

  • 选择合适的编辑器。它可以为我们省去关注许多基础的编码规范的时间。例如,选择 PyCharm 比 CMD/txt 更加智能,PyCharm 编辑器有语法检查和规范提示,效率肯定高了一大截。
  • 使用插件进行代码检查。比如常见的有checklist,检查很有必要,能及时避免对程序不友好的代码出现,建议工作中重视这一过程。
  • 增加代码评审环节。大多数公司都会有这个环节,要知道,评审代码能避免开发者出现的逻辑错误,也避免使用效率不高的语法而影响执行效率。

因此,编码过程中养成遵守编程规范的意识,不仅可以写出阅读体验良好的代码,也能写出在机器上具有健壮性和可移植性的代码,何乐而不为呢?!

二、基础的规范

万丈高楼平地起,基础规范也至关重要,比如:命名规范、良好的书写风格、注释规范、导包规范、字符串规范、函数规范等。

1、命名规范

变量名是由英文字母、数字、下划线组成,且不能以数字开头,变量命名时要科学严谨,切勿太长,切勿表达含糊,常见的规范要求有:

  • 不要使用 Python 关键字作为变量名;
  • 不要使用双下划线__开头和结尾的命名,这是 python 的保留名称;
  • 除了迭代器(循环控制语句)和计数器外,避免使用单字符命名;
  • 变量命名要表达出具体业务含义,不要嫌命名太长,或者采用缩写要写好注释说明;
  • 使用单下划线_ 和 双下划线__ 表示内部名称,比如模块中、函数中、类中、方法中等;
  • 面向对象中类的变量和方法名,使用单下划线__开头表示是类中受保护的变量和方法,使用双下划线__开头表示是类中私有的变量和方法;

命名参考:

2、书写风格

拥有良好的代码书写风格,是每个编码者素质的直接体现,常见的约定有:

  • 按行严格缩进代码,一般设置tab键(或4个空格)缩进;
  • 通常每个语句应独占一行,每行语句不超过80个字符,且每行语句没有英文符号结尾;
  • 合理的使用空格与空行,保持代码的整洁和紧凑,一组代码表达一个完整逻辑后应空一行;
  • 尽量保持上下文语义的一致性,比如变量,函数就近命名,而不是放到很远的位置;
  • 尽量不要在 if 条件语句,while 循环语句等使用英文括号,除非实现连接的地方才使用;
  • 不要滥用注释,关键代码处需要有注释说明(定义用途、业务含义等说明);
  • import 导包,建议多个模块导入按行书写,不要用逗号隔开写在同一行;

关于注释规范,比如,单行注释:可以用#,也可以用''' ''','''''' '''''' ;多行注释:用''' ''','''''' '''''',也可以用多个# 。注释要遵守 pydoc 规范:

  • 模块、函数、类、方法等,第一行的注释说明,建议使用 '''''' '''''' ;
  • 注释单行代码,可以在代码上方,也可以在代码右侧,在右侧时需要离开代码至少2个空格。

建议使用专业的编辑器书写,写完后美化代码,尽量避免低级错误。

3、字符串规范

关于字符串规范,禁止单引号和双引号混用,避免影响代码的可读性,目前字符串格式化支持 %、format()、f-string 等风格。使用字符串需注意:

  • %之间 或 {}之间 最好用逗号隔开,不要挤在一起。
# 建议 
print('欢迎%s,%s,来到杭州' % (name1, name2))
print('欢迎{},{},来到杭州'.format(name1, name2))
# 不建议 
print('欢迎%s%s,来到杭州' % (name1, name2))
print('欢迎{}{},来到杭州'.format(name1, name2))
  • 建议使用 f-string 替换 % 和 format() 进行格式化,但注意 Python3.6 以上版本才能使用。
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)

4、函数规范

在避免代码重复和冗余时,经常会用到函数,精心设计的函数不仅可以提高程序的健壮性,还可以增强可读性、减少维护成本。函数设计需要考虑的注意事项有:

  • 函数的命名要体现出实现的功能,同时入参的长度和数量也要合理;
  • 尽量避免逻辑过长的函数,对于流程控制语句的使用,不宜嵌套过深;
  • 要保证一个函数只做一件事,尽量保证函数语句粒度的一致性;
  • 考虑兼容性,思考是否存在需求变更或版本升级带来的修改函数的可能性;
  • 禁止在函数中定义可变对象作为默认值,使用异常替换返回错误以保证通过单元测试等。

5、提升效率的规范

<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))  # <class 'NoneType'>

使用 None 时需要注意它的特殊性,其类型为 NoneType,用来判断对象是否为空的。

<5>、函数 str() 和 repr() 的区分

二者都可以将 Python 中的对象转换为字符串,并且使用和输出都非常相似。对于不同类型的输入,这两个函数的输出有何异同,比较如下:

repr() 和 str() 对于大多数数据类型的输出基本一致,因此很容易混淆。二者有以下几点明显差异:

  • 在解释器中,输入a直接调用的是 repr() 函数,如果使用 print a,则调用的是 str() 函数。
  • 面向目标不同。str() 主要面向用户,返回形式为用户友好性和可读性都较强的字符串类型,而repr() 面向的是 Python 解释器,目的是准确性,其返回值表示解释器内部的含义,常被用做 debug 用途。
  • 这两个方法分别调用内建的__str__()和__repr__()方法,一般来说在类中都应该定义__repr__()方法,而__str__()方法则为可选,当可读性比准确性更为重要的时候应该考虑定义__str__()方法。如果类中没有定义__str__()方法,则默认会使用__repr__()方法的结果来返回对象的字符串表示形式。用户实现__repr__()方法的时候最好保证其返回值可以用eval()方法使对象重新还原。

<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 编码规范内容还有很多,这篇无法面面俱到,后面有时间将继续记录和分享。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值