1. 查询自己使用的Python版本
- 在命令行查询
$ python --version
Python 3.9.12- 在python脚本中执行
import sys
print(sys.version_info) # sys.version_info(major=3, minor=9, micro=12,releaselevel='final'serial=0)
print(sys.version)
"""
3.9.12 (main, Apr 5 2022, 06:56:58)
[GCC 7.5.0]
"""2. 遵循PEP 8风格指南
与空白有关的建议
空白(whitespace)在语法上相当重要。Python程序员对空白字符的用法尤其在意,因为它们会影响代码的清晰程度。在这方面,大家应该遵循以下几条建议:
- 用空格(space)表示缩进,而不要用制表符(tab)
- 和语法相关的每一层缩进都用4个空格表示
- 每行不超过79个字符
- 对于占据多行的长表达式来说,除了首行之外的其余各行都应该在通常的缩进级别之上再加4个空格
- 在同一份文件中,函数与类之间用两个空行隔开
- 在同一个类中,方法与方法之间用一个空行隔开
- 使用字典时,键与冒号之间不加空格,写在同一行的冒号和值之间应该加一个空格
- 给变量赋值时,赋值符号的左边和右边各加一个空格,并且只加一个空格就好
- 给变量的类型做注解(annotation)时,不要把变量名和冒号隔开,但在类型信息前应该有一个空格
与命名有关的建议
- 函数、变量及属性用小写字母来拼写,各单词之间用下划线相连,例如:lowercase_underscore
- 受保护的实例属性,用一个下划线开头,例如:_leading_underscore
- 私有的实例属性,用两个下划线开头,例如:__double_leading_underscore
- 类(包括异常)命名时,每个单词的首字母均大写,例如:CapitalizedWord
- 模块级别的常量,所有字母都大写,各单词之间用下划线相连,例如:ALL_CAPS
- 类中的实例方法,应该把第一个参数命名为self,用来表示该对象本身
- 类方法的第一个参数,应该命名为cls,用来表示这个类本身
与表达式和语句有关的建议
每件事都应该有简单的做法,而且最好只有一种
- 采用行内否定,即把否定词直接写在要否定的内容前面,而不要放在整个表达式的前面,例如应该写if a is not b,而不是if not a is b
- 不要通过长度判断容器或序列是不是空的,例如不要通过if len(somelist) == 0判断somelist是否为[]或”等空值,而是应该采用if not somelist这样的写法来判断,因为Python会把空值自动评估为False
- 如果要判断容器或序列里面有没有内容(比如要判断somelist是否为[1]或’hi’这样非空的值),也不应该通过长度来判断,而是应该采用if somelist语句,因为Python会把非空的值自动判定为True
- 不要把if语句、for循环、while循环及except复合语句挤在一行。应该把这些语句分成多行来写,这样更加清晰
- 如果表达式一行写不下,可以用括号将其括起来,而且要适当地添加换行与缩进以便于阅读
- 多行的表达式,应该用括号括起来,而不要用\符号续行
与引入有关的建议
- import语句(含from x import y)总是应该放在文件开头。引入模块时,总是应该使用绝对名称,而不应该根据当前模块路径来使用相对名称。例如,要引入bar包中的foo模块,应该完整地写出from bar import foo,即便当前路径为bar包里,也不应该简写为import foo
- 如果一定要用相对名称来编写import语句,那就应该明确地写成:from . import foo
- 文件中的import语句应该按顺序划分成三个部分:首先引入标准库里的模块,然后引入第三方模块,最后引入自己的模块。属于同一个部分的import语句按字母顺序排列 Python Enhancement Proposal 叫作PEP 8,它是一份针对Python代码格式而编订的风格指南,完整的风格指南
3. 了解bytes与str的区别
- bytes实例包含的是原始数据, 即8位的无符号值(通常按照ASCII编码标准来显示)
- str实例包含的是Unicode码点(code point,也叫作代码点),这些码点与人类语言之中的文本字符相对应。
# bytes
a = b'h\x65llo'
print(list(a)) # [104, 101, 108, 108, 111]
print(a) # b'hello'
# str
a = 'a\u0300 propos'
print(list(a)) # ['a', '̀', ' ', 'p', 'r', 'o', 'p', 'o', 's']
print(a) # à propos要把Unicode数据转换成二进制数据,必须调用str的encode方法。要把二进制数据转换成Unicode数据,必须调用bytes的decode方法
- 将bytes或者str转为str 或者 转为 bytes
def to_str(bytes_or_str):
if isinstance(bytes_or_str, bytes): # isinstance 判断一个对象的变量类型。
value = bytes_or_str.decode('utf-8')
else:
value = bytes_or_str
return value
def to_bytes(bytes_or_str):
if isinstance(bytes_or_str, str):
value = bytes_or_str.encode('utf-8')
else:
value = bytes_or_str
return value
# repr 返回对象的string格式 相当于全部加""
print(repr(to_str(b'foo'))) # 'foo'
print(repr(to_str("bar"))) # 'bar'
print(repr(to_bytes(b"foo"))) # b'foo'
print(repr(to_bytes("bar"))) # b'bar'- bytes与str这两种实例不能在某些操作符(例如>、==、+、%操作符)上面混用。
- 对于bytes的读取要用rb 写入用wb
with open('data.bin', 'rb') as f:
data = f.read()
with open('data.bin', 'wb') as f:
data = f.write(b'\xf1\xf2\xf3\xf4\xf5')4. 用支持插值的f-string取代C风格的格式字符串与str.format方法
内置的format函数与str类的format方法
可以使用help('FORMATTING')查看详细的str.format格式
######################
a = 1234.5678
formatted = format(a, ',.2f')
print(formatted) # 1234.57
######################
b = "my string"
formatted = format(b, '^20s') # ^表示居中对齐 总共20的宽度 s为string
print('*', formatted, '*') # * my string *
######################
key = 'my_var'
value = 1.234
formatted = '{} = {}'.format(key, value)
print(formatted) # my_var = 1.234
######################
key = 'my_var'
value = 1.234
formatted = '{:<10} = {:.2f}'.format(key, value) # :是标准格式 右边跟说明符 < 表示左对齐
print(formatted) # my_var = 1.23
######################
key = 'my_var'
value = 1.234
formatted = '{1} = {0}'.format(key, value)
print(formatted) # 1.234 = my_var
######################插值格式字符串(interpolated format string,简称f-string
# python 3.6 之后可以通过值格式字符串
key = 'my_var'
value = 1.234
formatted = f'{key} = {value}'
print(formatted) # my_var = 1.234
######################
key = 'my_var'
value = 1.234
formatted = f'{key!r:<10} = {value:.2f}' # !r可以转化为Unicode的repr形式的字符串
print(formatted) # 'my_var' = 1.23
######################5. 用辅助函数取代复杂的表达式
Python的语法相当简明,所以有时只用一条表达式就能实现许多逻辑。
from urllib.parse import parse_qs
# 拆分url
my_values = parse_qs('red=5&blue=0&green=',keep_blank_values=True)
print(repr(my_values))
# 不存在的值会返回None
print('Red: ', my_values.get('red')) # Red: ['5']
print('Green: ', my_values.get('green')) # Green: ['']
print('Opacity: ', my_values.get('opacity')) # Opacity: None
# 使用Boolean 将None替换为0
red = my_values.get('red', [''])[0] or 0
green = my_values.get('green', [''])[0] or 0
opacity = my_values.get('opacity', [''])[0] or 0
print(f'Red: {red!r}') # Red: '5'
print(f'Green: {green!r}') # Green: 0
print(f'Opacity: {opacity!r}') # Opacity: 0上面的表达式特别复杂 但是可以通过 if/else 结构写的更清晰
from urllib.parse import parse_qs
my_values = parse_qs('red=5&blue=0&green=',keep_blank_values=True)
red_str = my_values.get('red', [''])
red = int(red_str[0]) if red_str[0] else 0
# 在一步就可以写成辅助函数- Python的语法很容易把复杂的意思挤到同一行表达式里,这样写很难懂
- 复杂的表达式,尤其是那种需要重复使用的复杂表达式,应该写到辅助函数里面
- 用if/else结构写成的条件表达式,要比用or与and写成的Boolean表达式更好懂
6. 把数据结构直接拆分到多个变量里,不要专门通过下标访问
- 通过拆包(unpacking)的方式将元祖分别赋给多个变量
- 通过unpacking来赋值要比通过下标去访问元组内的元素更清晰,而且这种写法所需的代码量通常比较少
- 赋值操作的左边除了可以罗列单个变量,也可以写成列表、序列或任意深度的可迭代对象(iterable)
item = ("Peanut butter", "Jelly")
first, second = item # Unpacking
print(first, 'and', second) # Peanut butter and Jelly7. 尽量用enumerate取代range
Python内置的range函数适合用来迭代一系列整数
直接迭代
如果要迭代某种数据结构, 例如字符串列表,不用专门通过range设定范围迭代, 可以直接迭代
flavor_list = ['vanilla', 'chocolate', 'pacan', 'strawberry']
for flavor in flavor_list:
print(f'{flavor} is delicious')
"""
vanilla is delicious
chocolate is delicious
pacan is delicious
strawberry is delicious
"""enumerate迭代
如果在迭代过程中想要知道list里的位置, 可以通过enumerate(一种迭代器,封装成惰性生成器)实现
flavor_list = ['vanilla', 'chocolate', 'pacan', 'strawberry']
it = enumerate(flavor_list)
print(next(it)) # (0, 'vanilla')
print(next(it)) # (1, 'chocolate')
# 或者使用umpacking
for i, flavor in enumerate(flavor_list):
print(f'{i + 1}: {flavor}')
"""
1: vanilla
2: chocolate
3: pacan
4: strawberry
"""
# 也可以指定从1开始计算
for i, flavor in enumerate(flavor_list, 1):
print(f'{i}: {flavor}')8. 用zip函数同时遍历两个迭代器
写Python代码时,经常会根据某份列表中的对象创建许多与这份列表有关的新列表 可以使用列表推导式
names = ['Cecilia', 'Lise', 'Marie']
counts = [len(n) for n in names]
print(counts) # [7, 4, 5]改用zip函数(把两个以上的iterator封装成惰性生成器)
zip如果提供的迭代器的长度不一致,那么只要其中任何一个迭代完毕,zip就会停止
names = ['Cecilia', 'Lise', 'Marie']
counts = [len(n) for n in names]
longest_name = None
max_count = 0
for name, count in zip(names, counts):
if count > max_count:
longest_name = name
max_count = count
print(longest_name, max_count) # Cecilia 7如果想按最长的那个迭代器来遍历,那就改用内置的itertools模块中的zip_longest函数
import itertools
names = ['Cecilia', 'Lise', 'Marie']
counts = [len(n) for n in names]
names.append('Rosalind')
for name, count in itertools.zip_longest(names, counts):
print(f'{name}: {count}')
"""
Cecilia: 7
Lise: 4
Marie: 5
Rosalind: None
"""如果其中有些列表已经遍历完了 那么zip_longest会用当初传给fillvalue参数的那个值来填补空缺(本例中空缺的为字符串’Rosalind’的长度值),默认的参数值是None
9. 不要在for与while循环后面写else块
- Python有种特殊的语法,可以把else块紧跟在整个for循环或while循环的后面
- 只有在整个循环没有因为break提前跳出的情况下,else块才会执行
- 把else块紧跟在整个循环后面,会让人不太容易看出这段代码的意思,所以要避免这样写
10. 用赋值表达式减少重复代码
赋值表达式(assignment expression)是Python 3.8新引入的语法,它会用到海象操作符(walrus operator)。这种写法可以解决某些持续已久的代码重复问题。a = b是一条普通的赋值语句,读作a equals b,而a := b则是赋值表达式,读作a walrus b。这个符号为什么叫walrus呢?因为把:=顺时针旋转90º之后,冒号就是海象的一双眼睛,等号就是它的一对獠牙。
if/else 用法
# 海象运算符之前
age = 20
if age > 18:
print('age is {age}')
# 使用海象表达式之后
if (age:=20) > 18:
print('age is {age}')while用法
# 海象运算符之前
while True:
name = "Marry"
if name == "Marry":
print('name is {name}')
# 使用海象表达式之后
while True:
if (name := "Marry") == "Marry":
print('name is {name}')- 赋值表达式通过海象操作符(:=)给变量赋值,并且让这个值成为这条表达式的结果,于是,我们可以利用这项特性来缩减代码
- 如果赋值表达式是大表达式里的一部分,就得用一对括号把它括起来
- 虽说Python不支持switch/case与do/while结构,但可以利用赋值表达式清晰地模拟出这种逻辑