19. 不要使用拆包的方式处理三个以上的返回值

  • 拆包多个返回值很可能出现顺序错误、行长度过长的问题;
  • 如有必要,可以使用轻量级的类或 namedtuple 来避免类似情况。

20. 尽量用异常来表示特殊情况,而不要返回 None

  • raise ... from ... 可以根据现有的异常继续向上抛出,表明异常的根源;
  • 函数遇到特殊情况应抛出异常,调用者需要编写代码处理这些异常。由于函数接口不会包含异常的定义,因此需要在函数文档中包含可能抛出的异常。

21. 了解如何在闭包里使用外围作用域中的变量

  • 表达式中引用变量时,作用域的遍历顺序为:当前函数作用域、外围作用域(外围函数等)、模块/全局作用域、内置作用域(包含内置函数的部分)。而在赋值时,只会查找当前函数作用域
  • nonlocal 可以在赋值时在上层作用域查找该变量(适用于闭包),但不能延伸到全局作用域(全局作用域需要用 global);
  • 不要滥用 nonlocal,仅在极其简单的函数中使用。可以将相关的状态封装成辅助类。

22. 用数量可变的位置参数减少视觉杂讯

  • 变长参数在传给函数时,总是要先转化成元组。因此可能需要解析生成器,导致内存耗尽。因此只有当我们确认参数个数较少时,才使用 star args;
  • 如果要添加新的位置参数,那么只能在 star args 之前添加(否则就是关键字参数),而这样会破坏现有调用的正确性。

23. 用关键字参数来表达可选的行为

  • 关键字参数顺序不限,位置参数必须在关键字参数之前,每个参数最多指定一次;
  • 关键字参数可以阐明每个参数的意图;
  • 带默认值的关键字参数可与原代码中的调用保持兼容;
  • 在调用时,可选的关键字参数总是应该以关键字形式指定,而不要用位置参数

24. 用 None 和文档字符串来描述具有动态默认值的参数

  • 参数的默认值会在模块加载的时候直接求出,不会动态改变
  • 动态默认值:设为 None,并在 docstring 中把实际行为描述出来;
  • 类型标注中可以使用 Optional[...],用来表示这个参数可能为目标类型或 None

25. 用只能以关键字、位置形式指定的参数来确保代码清晰

  • * 表示位置参数的结尾与仅关键字参数的开始;
  • (Python 3.8 开始)/ 之前的参数表示仅位置参数
  • 总结:positional-only, /, positional or keyword, *, keyword-only

26. 用 functools.wraps 定义函数装饰器

  • 使用一般的装饰器装饰函数后,由于会新建内层的函数,隐藏原函数的属性(__name____module__ 等),因此会影响调试器等使用内省机制的工具,如 help 函数等;
  • functools.wraps 运用到内层函数,它就会把内层函数的元数据复制到外围函数。
def trace(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        ...
    return wrapper