Effective Python¶
原书第二版
第1章 培养 Pythonic 思维¶
2. PEP 8 风格指南¶
空白有关
- 缩进用 4 个空格代替
命名相关
- 函数,变量及属性用小写字母和下划线连接,类名每个首字母都大写(不用下划线),模块级别的常量所有字母大写并用下划线连接
- 类中的实例方法第一个参数为 self(表示对象本身),类方法第一个参数为 cls(表示类本身)
表达式和语句相关
- 判断容器或序列有没有内容要用 if not somelist 或者 if somelist
- 多行的表达式用括号括起来,而不要用
\
3. str 和 bytes¶
bytes 是 8 位值所组成的序列,str 是由 Unicode 码点组成的序列,两者不能混用
def to_str(bytes_or_str):
if isinstance(bytes_or_str, bytes):
value = bytes_or_str.decode('utf-8')
else:
value = bytes_or_str
return value
# to_bytes 同理
4. 支持插值的 f-string¶
places = 3
number = 1.23456
print(f'my number is {number:.{places}f}') # python3.6
>>>
my number is 1.235
f"{n!r}" # 调用 repr()
f"{n!a}" # 调用 ascii()
f"{n!s}" # 调用 str()
7. enmuerate 取代 range¶
8. zip 函数遍历两个迭代器¶
zip 的循环次数由最短的列表决定,如果要全部迭代完,可以使用 zip_longest
9. 赋值表达式减少重复代码¶
:=
海象操作符,需要 python 3.8
a = 1 # 此表达式没有值
a := 1 # 此表达式值为 1
第2章 列表与字典¶
11. 序列切片¶
a[2:7] = [1, 2] # list 切片赋值时元素个数可以不同,这样会改变 list 的长度
b = a[:] # 给 a 列表做新的副本
b = a # 这么做不会给 b 分配新的列表,a 改变后 b 也会变
14. sort 方法的 key 参数¶
列表中内置的 sort 函数
list1.sort(key, reverse=False)
-
reverse:用来控制是否逆序
-
key:对于自定义的类型要进行排序,输入一个函数():
list1.sort(key = lambda x: (x.name, x.weight)) # 从 x 中取出了 2 个属性来进行比较, 元组可以直接进行比较
def fun(x):
pass
list1.sort(key=fun)
如果要对某些属性升序其他属性降序,如果可以用负号就用负号,否则只能多次使用 sort 方法
16. 用 get 处理键不在字典的情况¶
dict1.get(key, val) # val 为 key 不存在时返回的默认值
17. defaultdict 处理内部状态中缺失元素¶
defaultdict 用来避免普通字典的 keyerror,如果访问时 key 不存在会自动调用参数中的函数,fun 函数不能有参数
from collections import defaultdict
data = defaultdict(fun)
# eg
data = defaultdict(set)
18. __missing__ 构造依赖键的默认值¶
继承 dict 并实现 __missing__ 方法,定义了字典中不存在这个 key 时的操作
class Picture(dict):
def __missing__(self, key):
value = open_picture(key) # 一个函数,返回文件句柄
self[key] = value
return value
第3章 函数¶
21. nonlocal 和 global¶
nonlocal 在函数中表明某变量为此函数作用域外面的变量,否则会定义一个新的同名变量
global 在描述某变量并赋值后,系统会把此变量放到全局作用域
但 nonlocal 和 global 都不应该被滥用,nonlocal 用法比较复杂时最好使用赋值类
class Sorter:
def __init__(self, group):
self.group = group
self.found = False
def __call__(self, x):
if x in self.group: # 如果在 group组中的优先级比较高
self.found = True # found 不用 nonlocal
return (0, x)
return (1, x)
sorter = Sorter(group)
numbers.sort(key=sorter)
22. 数量可变的位置参数¶
def fun(args1, *varargs):
pass
这种做法适合参数数量不太多的情况
且要添加新的参数时比较麻烦,为了避免漏洞,应该使用 keyword-only argument (25条) 或者添加类型注解
23. 关键字参数¶
def fun(args1, **kwargs):
pass
好处:
- 易懂
- 带默认值
- 易扩展
24. 用 None 来描述默认值可变的参数¶
如默认返回当前的时间
不要用如 {}
充当默认参数,会有问题
25. keyword-only arg 和 positional-only arg¶
# keyword-only arg
def safe_div(number, divisor, *,
ignore_overflow=False, # 后面2个参数必须使用关键字指定
ignore_zero_div=False):
pass
# positional-only arg:python 3.8新特性
# 为了避免函数的关键字改名之类的情况
def safe_div(number, divisor, /, *, # / 左边的必须按照位置指定
ignore_overflow=False, # 后面2个参数必须使用关键字指定
ignore_zero_div=False):
pass
# -------
# 处在 / 和 * 之间的参数关键字和位置指定都可以
# -------
26. functools.wrap 定义函数修饰器¶
自己写修饰器会有很多问题
from functools import wraps
def trace(func):
@wraps(func)
def wrapper(*args, **kwargs):
...
return wrapper
@trace
def fun(n):
...
第4章 推导与生成¶
comprehension generator
27. 推导式¶
列表/字典/集合推导式
a = [1, 2, 3, 4, 5]
[x for x in a if x > 3 if x % 2 == 0] # 可以使用多个if,这些 if 表示同时成立
[x for x in a if x > 3 and if x % 2 == 0] # 效果相同
29. 用赋值表达式减少推导式中的代码¶
has_bug = {name: get_batches(stock.get(name, 0), 8)
for name in order
if get_batches(stock.get(name, 0), 8)}
has_bug = {name: batches # 对比上面的写法
for name in order
if batches :=get_batches(stock.get(name, 0), 8)}
# 赋值表达式会有作用域问题
30. 用 生成器 yield, 不要让函数返回列表¶
it = index_word_iter(address) # 此函数内用的 yield
print(next(it)) # 用 next 取值
print(list(it)) # 也可以把迭代器传递给 list 得到 list
31. 谨慎迭代函数收到的参数¶
我的理解是如果参数是一个迭代器,那么这个迭代器只能完整的迭代一次
解决方案:
- 在函数内复制一份数据, list(arg), 缺点是数据量大时内存消耗大
- 参数从迭代器变成返回迭代器的函数,可以使用 lambda
- 新建一个容器类,实现迭代器协议
32. 生成器表达式(替代数据量较大的列表推导式)¶
it = (len(x) for x in open("my_file.txt"))
print(it)
>>> <generator ...>
# 可以嵌套
roots = ((x, x**0.5) for x in it) # it 为上面得到的 iter
要注意生成器只能迭代一次
33. yield from¶
34.¶
(买的书缺了9页....)
35.¶
36.¶
第5章 类与接口¶
37. 用组合起来的类实现多层结构,不要用嵌套的内置类型¶
科目成绩->学生->成绩册
38. 用简单接口接受函数,而不是类的实例¶
方法也可以作为挂钩(hook),如果类定义了 __call__,那么类的对象也可以作为挂钩(callable)
39. @classmethod 多态¶
40. 使用 super 初始化超类¶
如果直接调用父类的初始化函数,在一些情况下会有问题,比如菱形继承
class MyBaseClass:
def __init__(self, value):
self.value = value
class MyChildClass(MyBaseClass):
def __init__(self):
MyBaseClass.__init__(self, 5) # 直接调用父类的初始化函数
使用 super() 在多继承时会遵循 C3 线性化算法,避免多次调用菱形继承中共同基类的构造函数; 且超类改名后也不用改程序
可以通过 mro() 方法 来查看超类的初始化顺序(P139); MRO(method resolution order): 方法解析顺序
41. mix-in 类¶
43. 自定义容器类型从 collections.abc¶
from collections.abc import Sequence
class MySequence(Sequence):
pass
通过这种方式,如果自定义容器有某些方法没有实现的时候,在实例化时会报错