1.python版本选择
python目前的版本分为python2和python3,并且这两个版本并不兼容。笔者写这篇文章的时候是2022-05-03,此时python2早已停止了维护(2020年1月1日,python2停止更新维护)。建议新入手的python使用者选择python3。如果你的项目深度依赖于python2代码库,那么可以考虑2to3与six工具来过渡到python3。
2.PEP8风格指南
PEP8风格指南的是python代码风格的指南,如果大家的python代码都 遵照PEP8风格,那么就让代码的易读性大大提高。PEP8随着python的 发展也在更新。大家可以查看PEP8指南。
与空白有关的建议
- 用空格表示缩进,不要用制表符;
- 每一层代码都应该缩进4个空格;
因此,建议大家设置tab键为4个空格。
- 对于占据多行的长表达式而言,除了首行之外的其余各行都应该在通常的缩进级别上在加上4个空格;
- 在同一份代码文件中,函数与类之间用两行空行隔开;
- 在同一个类中的方法与方法之间,使用一行空行隔开;
- 使用字典时,键与冒号之间不加空格,值与冒号之间加一个空格;
- 给变量赋值的时候,赋值符号左右各加一个空格即可;
- 给变量做类型注解的时,变量名和冒号之间不加空格,在类型和冒号之间加一个空格。
与命名有关的建议
- 函数,变量以及属性用小写字母,各个单词之间用下划线相连;
- 受保护的实例属性,用一个下划线开头;
- 私有的实例属性,用两个下划线开头;
- 类和异常命名时,每个单词的首字母需要大写;
- 模块基本的常量,每个字母都大写,单词之间使用下划线相连;
- 类的实例方法的第一个参数应该被命名为self,用来表示对象本身;
- 类方法的第一个参数应该命名为cls,用来表示类本身。
与表达式和语句有关的建议
- 采用行内否定,把否定词直接写在需要否定的内容前面。例如:
if a is not b
,而不是if not a is b
; - 不要通过长度来判断容器或者序列是不是空,而是应该使用
if not xxx
来判断不为空,python会自动判断空值为False
,非空值为True
; - 除了在列表推导之外,不要讲
if while execpt
等写在一行; - 如果表达式一行写不下,可以使用圆括号包括起来,然后在适当的地方换行,不建议使用从C/C 处借鉴的符号续行。
与引入有关的建议
import
语句总是应该放在文件开头;- 引入模块的时候,应该使用绝对名称,而不是使用相对名称。例如,当前路径有名为bar的模块,应该使用
from bar import foo
,而不是使用import bar
; - 如果一定要使用相对名称,那么应该明确的写为
from . import foo
; - 文件中的import语句应该按三个顺序来引入。首先引入标准库中的模块,其次引入第三方模块,最后引入自己的模块。
pythonic
pythonic这个词用来形容python开发界的特定风格,这种风格是大家在写代码的过程中逐渐形成的习惯。
python之禅中提到的python代码设计哲学“每件事都应该有简单的做法,而且最好只有一种”。python开发者不喜欢写复杂的代码,而是喜欢直观,简单且易懂的方式编写代码。
3.bytes和str的区别
bytes
bytes实例包含的是原始数据,即8位无符号值。
在网络传输,读写二进制文件的时候,通常使用bytes类型。
str
str实例包含的是Unicode码点。
在python3中,默认的编码方案是utf-8.
在编写python代码的时候,一定要把解码和编码操作放在最外层来做,让程序的核心部分可以使用Unicode数据来操作,这种办法叫做Unicode三明治。程序核心应该使用str类型的Unicode数据,并且最好使用utf-8编码。
bytes和str的区别
python3是强类型语言,bytes和str是两种不同的数据类型,它们之间不能直接运算。而是必须转换为统一的数据类型。通常是通过下面这两个辅助函数来完成。
代码语言:javascript复制def to_str(bytes_or_str):
"""bytes转str"""
if isinstance(bytes_or_str, bytes):
value = bytes_or_str.decode('utf-8')
else:
value = bytes_or_str
return value
def to_bytes(bytes_or_str):
"""str转bytes"""
if isinstance(bytes_or_str, bytes):
value = bytes_or_str
else:
value = bytes_or_str.encode('utf-8')
return value
操作文件
在操作文件的时候,python3内置的open函数返回的文件句柄默认使用Unicode字符串操作,而不是bytes实例,除非在open函数中指定以二进制方式打开文件。例如:
代码语言:javascript复制open('data.bin', 'rb') # 以二进制只读方式打开
open('data.txt', 'r') # 以文本只读方式打开
4.使用f-string取代C风格的格式字符串和str.format方法
在python3.6中引入了f-string来进行格式字符串,它解决了C风格的格式字符串和str.format带来的缺点。
C风格的格式字符串
对于大多数人而言,C风格的格式字符串都是熟悉的。因为很多语言的格式字符串写法都来自于C语言的printf函数。python也支持C风格的格式字符串,例如:
代码语言:javascript复制name = "Zhao Si"
age = 18
print("name is %s, age is %d" % (name, age)) # C风格的格式字符串
Effective Python中作者提到的几个缺点确实在经常使用python做命令行应用程序开发或者小脚本的时候,深有体会。下面是4个缺点。
如果%号右侧的元组值在类型或顺序上有变化,那么将可能导致错误。这个错误在C这种静态类型语言中在编译的时候会被指出,但是python是解释性的动态类型语言,不容易发现这个错误。(因此,我们对python这种动态类型语言做静态类型检查是必要的)
在填充模板之前,经常需要对值做一些处理,但是这样就会导致表达式会很长,可读性下降。
如果想用同一个值来填充格式字符串里的多个位置,那么就必须在%右侧的元组中多次重复该值。
python的%允许我们用dict来取代tuple,这样就可以避免1,3两个缺点。但是会将第2个缺点放大。并且带来了大量的废话书写。每个键至少要书写两次,不够简单。例如:
str.format
后来python3引入了str.format的高级字符串格式化机制,它比C风格的格式字符串要更加强大。但是依旧没有解决上面提到的几个问题,因此在python3.6的时候引入了f-string,一个str.format例子如下所示:
代码语言:javascript复制name = "Zhao Si"
age = 18
gender = "man"
template = "name is {name}, age is {age}, gender is {gender}"
formated = template.format(name=name, age=age, gender=gender)
print(formated)
这种写法比C风格的格式字符串见到了一些,但是并没有完全解决2,4两个缺点。str.format引入了一套迷你规则来控制format,可以通过交互式终端输入help('FORMATTING')
来查看这套规则。
f-string
f-string可以很好的解决上面提到的所有问题。新的语法要求在字符串之前加上F(f)来作为前缀形成f-string.
f-string把格式字符串的表达能力发挥到极致,它彻底解决了上面的第4个缺点,也就是键名重复导致的冗余问题。例如:
代码语言:javascript复制key = 'my_var'
value = 1.234
formatted = f'{key} = {value}'
print(formatted)
并且,f-string也支持str.format方法所支持的那套迷你语言。同一个问题,使用f-string方式比C风格的格式字符串和str.format风格都要简单,优雅的多。这也和python之禅所追求的目标一致。
5.用辅助函数取代复杂的表达式
由于python的语法非常简明(不是简单),所以有时候只用一条表达式就能实现许多逻辑。例如,求一句英文中最后一个单词的长度。
代码语言:javascript复制s = "I am a developer"
print(len(s.split(' ')[-1])) # 求最后一个单词长度并输出
一行代码即可求解,但是这样的表达式是复杂的,因此应该考虑把它拆分开来,形成多个部分,并且把这套逻辑写道辅助函数中,这样虽然多了几行代码,但是可以让程序更加清晰,更加容易修改,所以总体而言还是非常值得使用辅助函数来代替复杂表达式。
6.把数据结构拆分到多个变量里,不用使用下标访问
这个问题,实际上可以通过namedtuple来代替tuple从而得到解决。当然,我们也可以通过类中的属性来解决。但是这里介绍的是使用多个变量来替代。也就是主要介绍解包unpacking,例如:
代码语言:javascript复制people = ("Zhao Si", 18, "man")
name, age, gender = people
通过unpacking,可以让代码更加清晰,通过使用变量来操作相应的值,而且只需要一行代码即可把people中的多个值赋给相应的变量。
7.尽量用enumerate取代range
在我们需要通过下标操作的时候,通常会使用range函数。例如:
代码语言:javascript复制num_list = [1, 3, 5, 11, 23, 58, 98, 23]
for i in range(len(num_list)):
print(f"{i}: {num_list[i]}")
这种写法需要我们先求取列表的长度,然后根据长度来缺点循环次数,最后使用每次循环的i作为下标。这样写就很C语言,因此python提供了enumerate函数来解决刚才的问题,enumerate能够把任何一种迭代器封装为惰性生成器。每次循环的时候,enumerate能够给出下一个值以及本次循环的次数。另外,还可以通过enumerate的第二个参数来指定起始序号(默认为0),注意不要超出范围。下面通过next来展示enumerate的作用。
代码语言:javascript复制num_list = [1, 3, 5, 11, 23, 58, 98, 23]
it = enumerate(num_list) # it是个迭代器
print(next(it))
print(next(it))
print(next(it))
输出结果如下所示:
代码语言:javascript复制(0, 1)
(1, 3)
(2, 5)
现在,我们改写上面的for循环如下:
代码语言:javascript复制num_list = [1, 3, 5, 11, 23, 58, 98, 23]
for i, num in enumerate(num_list):
print(f"{i}: {num_list[i]}")