Python编程需要遵循的一些规则v2
Python编程需要遵循的一些规则v2
使用 pylint
pylint 是一个在 Python 源代码中查找 bug 的工具. 对于 C 和 C++ 这样的强类型静态语言来说, 这些 bug 通常由编译器来捕获. 由于 Python 的动态特性, 有些警告可能不对. 不过虚报的情况应该比较少. 确保对你的代码运行 pylint. 在 CI 流程中加入 pylint 检查的步骤. 抑制不准确的警告, 以便其他正确的警告可以暴露出来。
自底向上编程
自底向上编程(bottom up): 从最底层,依赖最少的地方开始设计结构及编写代码, 再编写调用这些代码的逻辑, 自底向上构造程序.
采取自底向上的设计方式会让代码更少以及开发过程更加敏捷.
自底向上的设计更容易产生符合单一责任原则(SRP) 的代码.
组件之间的调用关系清晰, 组件更易复用, 更易编写单元测试案例.
如:需要编写调用外部系统 API 获取数据来完成业务逻辑的代码.
应该先编写一个独立的模块将调用外部系统 API 获取数据的接口封装在一些函数中, 然后再编写如何调用这些函数 来完成业务逻辑.
不可以先写业务逻辑, 然后在需要调用外部 API 时再去实现相关代码, 这会产生调用 API 的代码直 接耦合在业务逻辑中的代码.
防御式编程
使用 assert 语句确保程序处于的正确状态 不要过度使用 assert, 应该只用于确保核心的部分.
注意 assert 不能代替运行时的异常, 不要忘记 assert 语句可能会被解析器忽略.
assert 语句通常可用于以下场景:
确保公共类或者函数被正确地调用 例如一个公共函数可以处理 list 或 dict 类型参数, 在函数开头使用
assert isinstance(param, (list, dict))
确保函数接受的参数是 list 或 dictassert 用于确保不变量. 防止需求改变时引起代码行为的改变
if target == x:
run_x_code()
elif target == y:
run_y_code()
else:
run_z_code()
假设该代码上线时是正确的, target 只会是 x, y, z 三种情况, 但是稍后如果需求改变了, target 允许 w 的 情况出现. 当 target 为 w 时该代码就会错误地调用 run_z_code, 这通常会引起糟糕的后果.
使用 assert 来确保不变量
assert target in (x, y, z)
if target == x:
run_x_code()
elif target == y:
run_y_code()
else:
assert target == z
run_z_code()
不使用 assert 的场景:
不使用 assert 在校验用户输入的数据, 需要校验的情况下应该抛出异常
不将 assert 用于允许正常失败的情况, 将 assert 用于检查不允许失败的情况.
用户不应该直接看到 AssertionError, 如果用户可以看到, 将这种情况视为一个 BUG
避免使用 magic number
赋予特殊的常量一个名字, 避免重复地直接使用它们的字面值. 合适的时候使用枚举值 Enum.
使用常量在重构时只需要修改一个地方, 如果直接使用字面值在重构时将修改所有使用到的地方.
建议
GRAVITATIONAL_CONSTANT = 9.81
def get_potential_energy(mass, height):
return mass * height * GRAVITATIONAL_CONSTANT
class ConfigStatus:
ENABLED = 1
DISABLED = 0
Config.objects.filter(enabled=ConfigStatus.ENABLED)
不建议
def get_potential_energy(mass, height):
return mass * height * 9.81
# Django ORM
Config.objects.filter(enabled=1)
处理字典 key 不存在时的默认值
使用 dict.setdefault 或者 defaultdict
# group words by frequency
words = [(1, 'apple'), (2, 'banana'), (1, 'cat')]
frequency = {}
dict.setdefault
建议
for freq, word in words:
frequency.setdefault(freq, []).append(word)
或者使用 defaultdict
from collections import defaultdict
frequency = defaultdict(list)
for freq, word in words:
frequency[freq].append(word)
不建议
for freq, word in words:
if freq not in frequency:
frequency[freq] = []
frequency[freq].append(word)
注意在 Python 3 中 map filter 返回的是生成器而不是列表, 在隋性计算方面有所区别
禁止使用 import *
原则上禁止避免使用 import *, 应该显式地列出每一个需要导入的模块
使用 import * 会污染当前命名空间的变量, 无法找到变量的定义是来哪个模块, 在被 import 的模块上的改动可 能会在预期外地影响到其它模块, 可能会引起难以排查的问题.
在某些必须需要使用或者是惯用法 from foo import * 的场景下, 应该在模块 foo 的末尾使用 all 控制被导出的变量.
# foo.py
CONST_VALUE = 1
class Apple:
...
__all__ = ("CONST_VALUE", "Apple")
# bar.py
# noinspection PyUnresolvedReferences
from foo import *