注册

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 或 dict

  • assert 用于确保不变量. 防止需求改变时引起代码行为的改变

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 *

作者:未来现相
来源:https://mp.weixin.qq.com/s/QinR-bHolVlr0z8IyhCqfg

0 个评论

要回复文章请先登录注册