本文目的
动态类型一时爽, 代码重构火葬场, 说的是: 动态语言在初期开发比较爽, 但是到后期维护起来比较困难. Python 作为动态语言之一, 自然也会有这样的缺点. 其实说火葬场, 也没有那么严重, 只要严格的遵守一组规范, 也能做到重构的时候, 也一样爽.
不以规矩不成方圆, 规范自然是十分重要的, 而在动态语言中, 尤其重要(很多人拿 Python 写脚本, 基本是随心所欲地写, 自然后期维护困难). 所谓兵马未动粮草先行, 我们应该在写代码前, 就做好充足的 "表面功夫".
本文不涉及哪些
不做文档复读机.PEP 8 中已经有了的, 就不重复了, 复述一些 "低级(大家都知道了的)" 内容, 没啥意思.
不迷信权威, 这里指Google Python Guide, 那是适合 Google 的规范, 并不是社区规范, 其实我觉得这份规范既不完整, 同时, 净是一些众人皆知的内容, 并不推荐之.
不搞宗教信仰, 奉承实用主义. 比如典型的import this, 只有我一个人觉得不过是一堆空洞的废话吗, 况且 Python 标准库中很多地方, 也没有做到import this 中的simple, explicit, and powerful. 每次各种文章 (无节操的营销文章, 以各种培训机构为主体) 提及这个东西, 我真是浑身不自在 & 尴尬(写代码是很工程很严肃的事情, 搞这种玄学干嘛呢).
适用范围 & 原则
Python 2.7 - Python 3.x . 虽然官方宣布了 Python 2 的寿命是 2020, 而且似乎现在 Python 3 已经是主流了. 但是同学, legacy code 可不是说去掉就去掉的, Python 2 仍然会存在相当长一段时间. 况且, Python 3 有相对于 Python 2 的 killer feature 吗, 并没有.
以 PEP 8 为蓝本, 紧紧团结在 PEP 8 周围. 任何非官方的文档, 只是参考之(同理, 本文亦是某种参考资料).
PEP 8 已经有了的, 就不要重复了.
规范
[强制 + 强制] [挑选静态检查工具, 并自始至终都严格使用]
简单来说, 就是:
- Pylint
- Flake8
- pytest
一开始就要使用, 并且从严使用(发点时间了解这几个工具, 带来的收益是无限的, 如果你是比较正式的项目的话).
[强制 +] [多写 UT]
其他编程语言, 同理.
有一份 UT 在手, 重构起来, 心里放心很多.
Python 的话, 只需要了解 unittest 就够了, pytest 也可以.
[强制] [文件编码 & Unicode] :
使用 4 空格缩进, 禁用任何 TAB 符号
源码文件使用 UTF-8 无 BOM 编码格式
总是使用 Unix \n 风格换行符
在每一个 py 文件头, 都添加如下内容:
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- #
- # 只导入 future 空间的这两个特性就够了, 其他特性容易造成其他方面的不兼容, 没有使用的必要性.
- from __future__ import (absolute_import, unicode_literals)
[强制] [命名]
class,function 该如何命名, 不赘述, 严格照着 PEP8 做就行了, 不用多想.
全局变量(全局变量, 一般是常量, 我们认为: 凡是全局的, 都是常量), 应该始终使用全大写, 如:
- GLOBAL_PUBLIC = "G1"
- _GLOBAL_PRIVATE = "G2"
- class Person:
- _GLOBAL_IN_CLASS = 'G3'
按照这条要求, 其实很多库 or 开源库, 都是不符合要求的. 为什么这么强硬呢?
Python 中的变量定义, 是不分声明,定义,初始化,赋值这几个概念的, 所以一个
a = 1
如果没有上下文, 你是很难确定其作用域的, 也很难确定 这到底是初始化还是赋值(a 已经存在过),
如果全局变量还不用全大写, 带来的麻烦只会更多.
如果始终坚持这个原则, 将会给代码的可读性带来极大提升.
定义枚举, 始终加 Enum 后缀; 定义异常始终加 Exception 后缀; 定义 mixin, 始终加 Mixin 后缀, 如
- class DirectionEnum:
- UP = 1
- DOWN = 2
- class MyException(Exception):
- pass
- class MyError(Exception):
- pass
- class SomeMixin:
- pass
[强制] [强化 private 的概念]
即: 最小知识原则, 对外暴露的东西越少越好
翻译成大白话就是:
1. 实例属性, 一般定义成 private 的
2. class, 对外提供的方法越少越好
3. module, 对外提供的接口越少越好
4. package, 对外提供的 module 越少越好
翻译成代码就是:
1. 项目布局
- package/
- __init__.py
- _private_mod.py
- public_mod.py
2. 某模块内容
- public_mod.py
- PUBLIC_GLOBAL = 'G1'
- _PRIVATE_GLOBAL = 'G2'
- class _Class:
- pass
- class PublicClass:
- _PRIVATE_GLOBAL = 'G3'
- def __init__(self, name,age):
- self._name = name
- self._age = age
- def public_method(self):
- pass
- def _private(self):
- pass
所有东西, 一开始就要定义成私有的, 等到确实需要开放访问了, 才开放出去.
[强制 & 重要] [关注公开接口的复杂性]
最好的接口是这样的, 调用者无脑使用
- def interface():
- pass
次等接口是这样的
- def interface(param1):
- pass
次次等接口是这样的
- def interface(p1, p2):
- pass
最大忍受限度的接口是这样的
- def interface(p1, p2, p3='SOME DEFAULT'):
- pass
- def interface(p1, *args):
- pass
不可接受的接口是这样的
- def interface(p1, p2, **kwargs):
- pass
令人无语的接口是这样的
- def interface(*args, **kwargs):
- # 尽量不要使用 **kwargs, 某些流行库有这样的毛病, 我是觉得: 极大地增加了调用者的心理负担, 反映了接口设计者的懒惰
- pass
一直觉得,**kwargs 只适用于极少数明确的场合, 并且需要辅以很明确的文档说明(解释为什么要使用), 然而现实是, 这个
特性已经被大家滥用了, 有必要单独说明之.
[推荐] [以 package 去设计命名空间, 而不是基于 module]
[推荐] [了解如下内容]
__init__.py 的作用
__main__.py 的作用
if __name__ == '__main__': 的作用
Python 的命名空间加载机制, 即: sys.path sys.modules 的内容
[推荐] [合理设计项目目录结构]
如果是使用某种框架(如 Django), 那么按照框架的规范来; 如果是 "非框架" 项目, 则按照如下结构
- project
- project/
- __init__.py
- core/
- utils/
- constants/
- __main__.py
- tests/
- docs/
- examples/
- README.md
- .pylintrc
- .flake8
来源: https://juejin.im/post/5afe94845188254267264da1