Skip to content

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

通过这种方式,如果自定义容器有某些方法没有实现的时候,在实例化时会报错

第6章

第7章

第8章

第9章