有效的 Python
Effective Python
Python 思维方式
第1条 知道你使用的 Python 版本
python --version
# Python 3.8.10
Python 3 通常是 python3
python3 --version
# Python 3.8.10
在运行中,可以通过 sys.version 来获取 python 的版本
>>> import sys
>>> print(sys.version_info)
sys.version_info(major=3, minor=8, micro=10, releaselevel='final', serial=0)
>>> print(sys.version)
3.8.10 (tags/v3.8.10:3d8993a, May 3 2021, 11:48:03) [MSC v.1928 64 bit (AMD64)]
第2条 遵循 PEP 8 编程风格
第3条 知道 bytes 和 str 的区别
bytes 实例包含的是原始数据,即8位无符号值
str 包含的是 Unicode 码点
如果需要从文件中读写二进制数据,那么需要用二进制的模式打开文件 “wb” 或 “rb”
如果需要从文件中读写文本数据,需要注意操作系统默认的编码影响,可以指定 encoding 编码来避免影响。
第4条 用 f-string 取代 C 风格的格式化或者 str.format 方法
三种方式对别如下
key = 'my_var'
f_string = f'{
key:<10} = {
value:.2f}'
str_args = '{:<10} = {:.2f}'.format(key, value)
str_kw = '{key:<10} = {value:.2f}'.format(key=key, value=value)
c_tuple = '%-10s = %.2f' % (key, value)
c_dict = '%(key)-10s = %(value).2f' % {
'key': key, 'value': value}
assert c_tuple == c_dict == f_string
assert str_args == str_kw == f_string
第5条 用辅助函数取代复杂表达式
- Python 的语法灵活导致可以写复杂且难以阅读的单行表达式
- 当重复用到同一逻辑时,可以把逻辑移到辅助函数中
- if else 表达式比 or/and 布尔运算更易读
from urllib.parse import parse_qs
def test_complex_expressions():
my_values = parse_qs('red=5&blue=0&green=', keep_blank_values=True)
red = int(my_values.get('red', [''])[0] or 0)
blue = int(my_values.get('blue', [''])[0] or 0)
green = int(my_values.get('green', [''])[0] or 0)
assert red == 5 and blue == 0 and green == 0
def test_if_else():
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
blue_str = my_values.get('blue', [''])
blue = int(blue_str[0]) if blue_str[0] else 0
green_str = my_values.get('green', [''])
green = int(green_str[0]) if green_str[0] else 0
assert red == 5 and blue == 0 and green == 0
def get_first_int(values, key, default=0):
found = values.get(key, [''])
if found[0]:
return int(found[0])
return default
def test_helper_function():
my_values = parse_qs('red=5&blue=0&green=', keep_blank_values=True)
red = get_first_int(my_values, "red", 0)
blue = get_first_int(my_values, "blue", 0)
green = get_first_int(my_values, "green", 0)
assert red == 5 and blue == 0 and green == 0
第6条 把数据结构直接拆分到多个变量里
snacks = [('bacon', 350), ('donut', 240), ('muffin', 190)]
for i in range(len(snacks)):
item = snacks[i]
name = item[0]
calories = item[1]
print(f'#{
i+1}: {
name} has {
calories} calories')
# 使用 enumerate 加上解包赋值方式简化代码
for rank, (name, calories) in enumerate(snacks, 1):
print(f'#{
rank}: {
name} has {
calories} calories')
第7条 用 enumerate 取代 range
flavor_list = ['vanilla', 'chocolate', 'pecan', 'strawberry']
for i in range(len(flavor_list)):
flavor = flavor_list[i]
print(f'{
i + 1}: {
flavor}')
# 使用 enumerate 比 range 代码更简洁
for i, flavor in enumerate(flavor_list):
print(f'{
i + 1}: {
flavor}')
第8条 用 zip 同时遍历多个迭代器
zip 可以同时遍历多个迭代器,zip 只迭代到最短的迭代器结束,如果需要迭代到最长的迭代器结束,可以使用 itertools.zip_longest
names = ['Cecilia', 'Lise', 'Marie']
counts = [len(n) for n in names]
longest_name = None
max_count = 0
for i, name in enumerate(names):
count = counts[i]
if count > max_count:
longest_name = name
max_count = count
# 使用 zip 同时遍历多个迭代器
for name, count in zip(names, counts):
if count > max_count:
longest_name = name
max_count = count
# 遍历最长的迭代器
import itertools
for name, count in itertools.zip_longest(names, counts):
print(f'{
name}: {
count}')
第9条 避免在 for 或 while 循环后使用 else
else 块只在循环没有被 break 时才会执行,容易引起混乱,尽量避免使用
第10条 用赋值表达式减少重复代码
count = fresh_fruit.get('lemon', 0)
if count:
make_lemonade(count)
else:
out_of_stock()
# 使用赋值表达式 := 简化为
if count := fresh_fruit.get('lemon', 0):
make_lemonade(count)
else:
out_of_stock()
count = fresh_fruit.get('banana', 0)
if count >= 2:
pieces = slice_bananas(count)
to_enjoy = make_smoothies(pieces)
else:
count = fresh_fruit.get('apple', 0)
if count >= 4:
to_enjoy = make_cider(count)
else:
count = fresh_fruit.get('lemon', 0)
if count:
to_enjoy = make_lemonade(count)
else:
to_enjoy‘= 'Nothing'
# 使用赋值表达式 := 来减少缩进
if (count := fresh_fruit.get('banana', 0)) >= 2:
pieces = slice_bananas(count)
to_enjoy = make_smoothies(pieces)
elif (count := fresh_fruit.get('apple', 0)) >= 4:
to_enjoy = make_cider(count)
elif count := fresh_fruit.get('lemon', 0):
to_enjoy = make_lemonade(count)
else:
to_enjoy = 'Nothing'
列表与字典
第11条 学会对序列做切片
a = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
print('Middle two: ', a[3:5])
print('All but ends:', a[1:7])
# 从开头开始切片不需要写 0
assert a[:5] == a[0:5]
# 切片到结尾时,不要写结束索引
assert a[5:] == a[5:len(a)]
# 当需要相对于尾巴进行切片时,使用负数索引
a[:-1]
# 当索引超出范围时,切片会忽略缺失值
first_twenty_items = a[:20]
last_twenty_items = a[-20:]
# list 的切片返回的是新的 list 对象,对切片的修改不会影响原来的 list
b = a[3:]
print('Before: ', b)
# ['d', 'e', 'f', 'g', 'h']
b[1] = 99
print('After: ', b)
# ['d', 99, 'f', 'g', 'h']
print('No change:', a)
# ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
# 对切片范围的赋值,会使原来的 list 收缩或变长
print('Before ', a)
# ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
a[2:7] = [99, 22, 14]
print('After ', a)
# ['a', 'b', 99, 22, 14, 'h']
a[2:3] = [47, 11]
print('After ', a