Python 编码风格规范
/ / 阅读数:1108摘要 :为了让团队的代码风格保持一致,使得个人的代码便于团队维护,我们根据项目实际开发场景制定了本规范。
一、 概述
通过制定本规范,希望达成如下目标:
- 代码的可读性 即使在注释和文档不那么全面的情况下也可以知道代码的大概功能。
- 风格保持一致 团队的代码风格保持一致,使得个人的代码便于团队维护。
- 培养良好的编码习惯 避免犯一些简单但是又很难查的错误。
二、 细则
2.1. 空行和换行
- 用两行空行分割顶层函数和类的定义;
- 类内方法的定义用单个空行分割;
- 函数内逻辑无关段落之间空一行;
if/for/while
语句中,即使执行语句只有一句,也必须另起一行;- 不要将多句语句写在同一行,尽管使用
;
合法; 单行代码最好不要超过 100 个字符,可以使用圆括号(推荐)或反斜杠续行。
- 配置 PyCharm 显示列宽提示:
File->Settings->Editor->Code Style->Right margin
- 配置 VIM 显示列宽提示:
set colorcolumn=100
- 配置 PyCharm 显示列宽提示:
反例:
def main(): logger.info("Flask is a microframework for Python based on Werkzeug, Jinja 2 and good intentions. And before you ask: It's BSD licensed!") if len(sys.argv) > 1 and sys.argv[1] == "Flask is a microframework for Python based on Werkzeug": pass |
正例:
def main(): logger.info("Flask is a microframework for Python based on Werkzeug, Jinja" " 2 and good intentions. And before you ask: It's BSD licensed!") # 注意为了避免容易和下面的缩进混淆,上面是2倍缩进 if (len(sys.argv) > 1 and sys.argv[1] == "Flask is a microframework for Python based on Werkzeug"): pass |
2.2. 编码
文件保存为 utf8 编码格式(文件头只写# -*- coding: utf-8 -*-
是不够的),便于中文注释的正确显示。
参考:http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
2.3. 导入
2.3.1. 通常应该在单独的行中导入
No:
import os, sys |
Yes:
import os import sys |
2.3.2. 尽量避免from xxx import *
用法
2.3.3. Imports 通常被放置在文件的顶部
- 仅在模块注释和文档字符串之后,在模块的全局变量和常量之前;
Imports 应该按序成组放置,且每组导入之间放置一个空行,每组最好按字母序排列:
- 先标准库的导入
- 再导入第二方、三方包
最后导入本应用的包
import os import sys import memcache import MySQLdb from myproject.lib.dblogic import get_user
- 对于内部包的导入是不推荐使用相对导入的,对所有导入都要使用包的绝对路径;
从一个包含类的模块中导入类时,通常可以写成这样:
from MyClass import MyClass from foo.bar.YourClass import YourClass
2.4. 文件头
设置编码方式
# -*- coding: utf-8 -*-
|
如果是程序入口文件,则文件最顶部还需要加上 shebang ,尾部加上 main 入口函数:
#!/usr/bin/env python def main(): ... if __name__ == '__main__': main() |
2.5. 注释
单行的注释用#
开头,并在#
后面加一个空格;成段的注释用"""
(三引号)。
如果代码内容修改了,及时更新注释。
如果注释无法做到及时更新,删除掉注释更好。
2.6. 命名风格
2.6.1. 目录
全小写单词或缩写,多个单词不用下划线间隔,比如:roomshow/
2.6.2. 文件
全小写单词或缩写,多个单词不用下划线间隔,比如:userclient.py
2.6.3. 类
每个单词首字母大写的单词或缩写,多个单词不用下划线间隔,比如:class AudioPass:
2.6.4. 函数
小写单词或缩写、下划线、数字组合,多个单词用下划线间隔,比如:update_user_age
如果是类里的私有函数以下划线开始,比如:def _private_func(self):
。
装饰器内部函数命名,采用加_
前缀的方式,方便异常时显示 Traceback:
def cache(fn): @functools.wraps(fn) def _cache(*args, **kw): # do something here return fn(*args, **kw) return _cache |
2.6.5. 变量
小写单词或缩写、下划线、数字组合,多个单词用下划线间隔,比如:conn_timeout
。
若与关键字名字冲突,后缀加划线,尽量不要使用缩略等其他方式,比如:from_
。
常用命名包括:
has_xxx
表示是否存在 xxxis_xxx
表示是否 xxxcan_xxx
表示是否能 xxx,是否有权限做 xxxexc
或ex
表示异常ret
、res
或resp
表示返回- 单词缩写除非有公认的缩写,否则用全部单词,不要自己创造缩写。
2.6.6. 常量
全大写单词或缩写、下划线、数字组合,多个单词用下划线间隔,比如:
class Code: OK = 0 ERROR = 1 INVALID_PARAM = 2 |
2.7. 缩进
缩进采用 4 个空格缩进,在项目根目录配置.editorconfig
文件:
# https://editorconfig.org root=true [*] indent_style=space indent_size=4 end_of_line=lf charset=utf-8 insert_final_newline=true trim_trailing_whitespace=true max_line_length=100 [*.yaml] indent_size=2 |
2.8. 函数、类、文件代码长度
函数、类、文件代码长度不要太长,如果太长拆分成多个。
函数建议 100 行之内,类或者文件 1000 行之内。
2.9. 空格
各种左右括号前不要加空格
No:
sum (1, 2), sum (1, 2), {"age": 18}
Yes:sum (1, 2), list [1, 2], {"age": 18}
逗号、冒号、分号前不要加空格,后面要加一个空格
No:
sum (1,2), sum (1 , 2), {"age":18}, {"age" : 18}
Yes:sum (1, 2), {"age": 18}
操作符左右各加一个空格,多行之间不要为了对齐增加空格
No:
age=18
Yes:age = 18
函数默认参数和 keyword 传参等号两边省略空格
No:
def get_user (name, age = 18): pass
Yes:def get_user (name, age=18): pass
单行注释符后面空一个空格,前面空至少 2 个空格
No:
width += 1 #border width
Yes:width += 1 # border width
三、 设计建议
3.1. 主循环要捕捉异常
避免程序因为没有考虑到的异常而直接退出。
3.2. None 比较
None
应该永远用:is
或is not
来做比较。
3.3. 对象类型的比较使用 isinstance
No: if type(obj) is type(1):
Yes: if isinstance(obj, int):
3.4. 使用 startswith ()、endswith ()
No: if foo[:3] == "bar":
Yes: if foo.startswith("bar"):
3.5. 空序列的判断
对于序列(字符串、列表、元组),使用空列表是false
这个事实,因此if not seq
或if seq
比if len(seq)
或if not len(seq)
好。
3.6. 布尔值比较
No: if greeting == True:
Yes: if greeting:
3.7. if else 判断
有if
的地方一定要有else
分支,做到不重不漏。如果else
不需要做事,需要明确写个注释,表示关注到了。
3.8. 序列是否存在某个 key
No:if dict.has_key(key):
Yes:if key in dict:
No: if name == "foo" or name == "bar":
Yes: if name in ("foo", "bar"):
3.9. 注意不要在 for/while 循环中修改被遍历的对象
user_ids = [100, 200, 300] for user_id in user_ids: print(user_id) if user_id == 200: user_ids.remove(user_id) |
比如上面的代码将不会打印 300,正确的做法是重新拷贝一份:
user_ids = [100, 200, 300] for user_id in user_ids[:]: # 或者 for user_id in copy.copy(user_ids): print(user_id) if user_id == 200: user_ids.remove(user_id) |
3.10. 时间相关的函数
- 获取当前时间:
datetime.datetime.now (), time.time ()
- 当前时间格式化为字符串:
time.strftime ("% Y-% m-% d")
- 字符串转成 datetime:
datetime.datetime.strptime ("2014-07-24", "% Y-% m-% d")
- 时间戳转成 datetime:
datetime.datetime.fromtimestamp (xxx)
- Datetime 转成时间戳:
time.mktime (xxx.timetuple ())
3.11. Url 访问
一定要设置超时时间,尽量使用requests
库,如果要使用urllib
,则不要直接拼凑参数:
import urllib import urllib2 def get_user(urs): url = "http://example.com/getuser" # 如果是用 GET 方式,注意不要直接拼url参数(url = url + '?name=' + name), # 要用 urlencode 或 quote 转义变量中可能包含的 /,& 等特殊字符,如下: # 1) url = url + '?' + urllib.urlencode(dict(urs=urs)) # 2) url = url + '?urs=' + urllib.quote(str(urs)) try: # urlopen 第 2 个位置参数不为 None 表示用 POST 方式 fp = urllib2.urlopen(url, dict(name=name), timeout=5) except urllib2.HTTPError as exc: return exc.code, exc.read() # http 错误时返回 return fp.code, fp.read() # http 正常时(2xx)返回 |
3.12. 日志
3.12.1 日志分级
DEBUG
用于程序调试,线上日志文件一般不会输出,但也应该尽量遵循日志的打印规范,方便临时打开 DEBUG 日志状态查看功能。INFO
用于记录程序运转和状态的信息,如:当前的 CPU、内存、任务队列、一定单位时间内的任务耗时等。 用于记录一过性的,不会大量反复输出的信息。如:连接数据库成功、连接缓存成功等后,便于跟踪程序进展信息。 用于记录关键性的信息,增加、删除、修改等操作。如:修改密码、创建房间、删除好友等。 用于记录一些异常但对程序和功能没有影响的信息。如:协议参数错误等。WARN
用于记录程序遇到一般性异常的情况,虽然暂时不影响程序和功能,但是极有可能运行在某种非正常的状态下,并且最终使程序功能异常。如:任务超时,请求过多等。ERROR
用于记录程序遇到严重错误,影响到程序的基本功能。如:数据库缓存连接错误或者失败,网络错误,跟其他服务的连接断开,程序异常退出,系统整体内存不足、CPU 过高等。 用于记录一些关键性的逻辑异常。如:扣钱等逻辑不符合预期等。
可以参考 日志的艺术 。
3.12.2 编写规范
INFO
及以上级别的日志必须打印较为完整的上下文信息,如请求的 user_id、参数、基本信息描述等。WARN
和ERROR
级别的日志尽可能记录程序异常的堆栈(如 Traceback),如错误发生在用户请求阶段必须记录用户当前请求的所有参数。
不应该打印的一些敏感信息,如用户密码等。
3.12.3 使用 logging.getLogger
使用logging.getLogger(__name__)
来获取logger
:
import logging logger = logging.getLogger(__name__) def main(): logger.info("hello world") |
四、 常见弃用写法
关键词 | 弃用写法 | 推荐写法 |
---|---|---|
as | except Exception, exc: |
except Exception as exc |
in | if map.has_key('foo'): |
if 'foo' in map: |
or | if name == 'cc' or name == '163': |
if name in ('cc', '163'): |
== | if foo[:3] == 'bar': |
if foo.startswith('bar'): |
type | if type(obj) is int: |
if isinstance(obj, int): |
class | class Foo: |
class Foo(object): |
None | if obj == None: |
if obj is None: |
True | if obj == True: |
if obj: or if obj is True: |
traceback | log.error('login error\n%s', traceback.format_exc()) |
log.exception('login error') or log.error('login error', exc_info=1) |
log | log.info('user login, urs=%s' % urs) |
log.info('user login, urs=%s', urs) |
五、 其他
其他未提及的编码规范请参阅 PEP 8 。
可以使用 git pre commit hooks 来自动检查和进行修正。
其他相关规范: