1首要:保持一致性原则
如果你只是改动一个大代码块的其中一小部分,那么请将你改动代码的编码风格与周围代码的编码风格保持一致
为了保持一致性,在下列情况下可以违背本规范:
- 遵循本规范会降低可读性
- 与周围其他代码不一致
- 代码在引入规范前完成,暂时没有理由修改
2代码布局
2.1缩进
-
每级缩进使用4个空格
-
括号中的续行应使用垂直隐式缩进或悬挂缩进
- 使用垂直隐式缩进时,续行应该与被圆括号、方括号、花括号包裹起来的其他元素对齐
- 使用悬挂缩进时,第一行不应该有参数,并使用缩进以区分自己时续行
# (垂直隐式缩进) 与左括号对齐 foo = long_function_name(var_one, var_two, var_three, var_four) # (悬挂缩进) 一般情况只需多加一层缩进 foo = long_function_name( var_one, var_two, var_three, var_four) # (悬挂缩进) 但下面情况,需再加多一层缩进,和后续的语句块区分开来 def long_function_name( var_one, var_two, var_three, var_four): print(var_one)
-
if语句条件跨行时,应添加额外的缩进
YES: # 额外添加缩进 if (this_is_one_thing and that_is_another_thing): do_something() NO: # 没有额外缩进,不推荐 if (this_is_one_thing and that_is_another_thing): do_something()
-
连续行多于两行时,应使用悬挂缩进,将右括号换行并回退缩进
# 右括号回退 my_list = [ 1, 2, 3, 4, 5, 6, ] result = some_function_that_takes_arguments( 'a', 'b', 'c', 'd', 'e', 'f', )
2.2续行
-
续行的首选方法是使用小括号、中括号、大括号,其次是反斜杠;如with语句中:
with open('/path/to/some/file/you/want/to/read') as file_1, \ open('/path/to/some/file/being/written', 'w') as file_2: file_2.write(file_1.read())
-
对于一般操作符,换行点应选择在操作符之后;对于运算符,换行点应在运算符之前;
class Rectangle(Blob): def __init__(self, width, height, color='black', emphasis=None, highlight=0): if (width == 0 and height == 0 and color == 'red' and emphasis == 'strong' or highlight > 100): raise ValueError("sorry, you lose") if width == 0 and height == 0 and (color == 'red' or emphasis is None): raise ValueError("I don't think so -- values are %s, %s" % (width, height)) Blob.__init__(self, width, height, color, emphasis, highlight)
income = (gross_wages + taxable_interest + (dividends - qualified_dividends) - ira_deduction - student_loan_interest)
-
对于链式语法,应在
.
前使用反斜杠续行,续行使用悬挂缩进YES: # 悬挂缩进 query = session.query(models.XXX) \ .filter_by(xxxxxxxxxxx) \ .first() # 全悬挂缩进 its_a_long_long_query = session \ .query( models.Accountinfo.id, models.Accountinfo.realname, models.Accountinfo.sex) \ .join(models.Customer.id==models.Accountinfo.id) \ .filter_by( shop_id=shop_id, id=self.current_user.id) \ .all() # “.” 换行悬挂缩进+括号中续行垂直隐式缩进 its_a_long_long_query = session \ .query(models.Accountinfo.id, models.Accountinfo.realname, models.Accountinfo.sex) \ .join(models.Customer.id==models.Accountinfo.id) \ .filter_by(shop_id=shop_id, id=self.current_user.id) \ .all() NO: query = session.query(models.Accountinfo.id, models.Accountinfo.realname, models.Accountinfo.sex) \ .join(models.Customer.id==models.Accountinfo.id) \ .filter_by(shop_id=shop_id, id=self.current_user.id) \ .all() its_a_long_long_query = session.query(models.XXX) \ .filter_by(xxxxxxxxxxx) \ .first()
-
每行尽量不要超过80个字符
2.3空行
- 使用两个空行分隔顶级函数定义、类定义
- 使用一个空行分隔类内的方法定义
- 额外的空行可以用来分隔几组相关的函数,但是要尽量减少使用
- 额外的空行可以在函数中用于分割不同的逻辑块,但是要尽量减少使用
2.4导入
-
一行导入语句中只导入一个库,库==模块?
YES: import os import sys from subprocess import Popen, PIPE NO: import sys, os
-
全局导入始终在文件的顶部,在模块注释和文档字符串之后,在模块全局变量和常量之前;
-
导入顺序按照标准库、第三方库、本地库顺序导入,各组导入之间应加一个空行;
-
使用绝对路径导入,通常可读性更好;在绝对路径比较长的情况下,也可以使用相对路径;
-
当在某个包含类的模块中导入类时,这样的书写方式是合理的:
from myclass import MyClass from foo.bar.yourclass import YourClass # 但如果这样的书写方式引起类名冲突,请这样书写 import myclass import foo.bar.yourclass # 使用 myclass.MyClass 和 foo.bar.yourclass.YourClass 来对其进行引用
-
禁止使用通配符导入
通配符导入
from <module> import *
应该避免,因为不清楚命名空间有哪些名称存在,混淆读者和许多自动化的工具。
3字符串引用
-
优先使用双引号字符串,如果在字符串中已包含单引号或双引号,那么就用另一个,避免反斜杆的使用,以免发生转义;
-
对于文档字符串,始终使用三重双引号:
def kos_root(): """ Return the pathname of the KOS root directory """ global _kos_root if _kos_root: return _kos_root ...
-
对于多行字符串,始终使用三重双引号而非三重单引号。
4空格的使用
-
括号里边避免空格;
# YES spam(ham[1], {eggs: 2}) # NO spam( ham[1], {eggs: 2} )
-
逗号、冒号、分号前不要加空格;
-
索引操作中的冒号当作操作符处理前后要有同样的空格;
-
函数的左括号前不要加空格;
-
序列的左括号前不要加空格;
-
操作符左右各加一个空格;
-
行末不要留多余的空格,空白行不要有任何空格;
-
二元运算符两边放置一个空格;
涉及 =、符合操作符 ( += , -= 等) 、比较 ( == , < , > , != , <> , <= , >= , in , not in , is , is not )、布尔 ( and , or , not )
-
优先级较高的运算符或操作符的前后不建议有空格;
# YES i = i + 1 submitted += 1 x = x*2 - 1 hypot2 = x*x + y*y c = (a+b) * (a-b) # NO i=i+1 submitted +=1 x = x * 2 - 1 hypot2 = x * x + y * y c = (a + b) * (a - b)
-
关键字参数和默认值参数的前后不要加空格;
# YES def complex(real, image=0.0): return magic(r=real, i=image) # NO def complex(real, image = 0.0): return magic(r = real, i = image)
-
函数注释中,
=
前后要有空格,:
和->
的前面无空格,后面有空格;# YES def munge(input: AnyStr): def munge(sep: AnyStr = None): def munge()-> AnyStr: def mubge(input: AnyStr, sep: AnyStr = None, limit=1000): # NO def munge(input: AnyStr=None): def munge(input:AnyStr): def munge(input: AnyStr)->PosInt:
5注释
5.1总体原则
与代码自相矛盾的注释比没注释更差,修改代码时要优先更新注释。
5.2块注释
- 块注释写在要注释的代码前,并和这些代码有同样的缩进;
- 块注释每行以
#
开头,#
后如果有内容,#
和内容之间必须留一个空格; - 块注释内的段落用仅包含单个
#
的行分割。
5.3行内注释
-
慎用行内注释;
-
行内注释是指注释和语句在同一行,放在语句之后,至少用两个空格和语句分开;
-
避免无谓的注释。
# YES x = x + 1 # Compensate for border # NO x = x + 1 # Increment x
5.4TODO注释
-
为所有的临时代码、占位代码编写TODO注释
-
代码审查过程中,遇到可能存在性能或逻辑问题的代码,但又不急于当时修改的,应编写TODO注释
-
TODO注释应该在所有开头处包含
TODO
字符串,紧跟着是用括号括起来的名字、email地址或其它标识符,然后是一个可选的冒号,接着必须有一行注释,解释要做什么。# TODO(kl@gmail.com): Use a "*" here for string repetition. # TODO(Zeke) Change this to use relations.
5.5文档字符串
-
为所有公共模块、函数、类、方法书写文档字符串;非公开方法不一定有文档字符串,但应有注释(出现在def行之后)来描述这个方法用来做什么;
-
多行文档字符串,结尾的
"""
应该单独成行;"""return a foobang optional plotz says to frobnicate the bizbaz first. """
-
单行的文档字符串,结尾的
"""
在同一行。
6命名规范
6.1首要原则
用户可见的API命名应遵循使用约定而不是实现
6.2总体原则
-
类名、方法名、变量名不要因为追求简短而使用晦涩的缩写;
-
模块命名尽量短小,使用全部小写的方式,可以使用下划线;
-
包命名尽量短小,使用全部小写的方式,不可以使用下划线;
-
类的命名使用 CapWords 的方式,模块内部使用的类采用 _CapWords 的方式;
-
异常命名使用 CapWords + Error 后缀的方式;
-
全局变量尽量只在模块内有效,前缀一个下划线;
-
函数命名使用全部小写的方式,可以使用下划线;
-
常量命名使用全部大写的方式,可以使用下划线;
-
类的属性(方法和变量)命名使用全部小写的方式,可以使用下划线;
-
类的属性有3中作用域 public、non-public 和 subclass API,可以理解成 C++ 中的 public、private、protected,non-public 属性前,前缀一条下划线,防止 import 时被导入;
-
类的属性若与关键字名字冲突,后缀一下划线,尽量不要使用缩略等其他方式;
-
为避免与子类属性命名冲突,在类的一些属性前,前缀两条下划线,会自动触发名字重组;比如:类Foo中声明
__a
,访问时,只能通过Foo._Foo__a
,避免歧义; -
实例方法第一个参数必须是self,类方式第一个参数必须是cls;
-
变量命名时,对于同一类的变量名称建议将区别项后置,例如:
session session_readonly session_statistic
6.3变量
- 常量:大写加下划线
USER_CONSTANT
- 私有变量:小写和一个前缀下划线
_private_value
- python中不存在私有变量这么一说,若是遇到需要保护的变量,使用小写和一个前置下划线;但这只是程序员之间的约定,用于警告说明这是一个私有变量,外部类不要去访问它;但实际上,外部类还是可以访问到这个变量。
- 内置变量:小写,两个前置下划线和两个后置下划线
__class__
6.4函数和方法
-
私有方法: 小写和一个前导下划线
def _secrete(self):
这里和私有变量一样,并不是真正的私有访问权限。同时也应该注意一般函数不要使用两个前导下划线(当遇到两个前导下划线时,Python 的名称改编特性将发挥作用)。特殊函数后面会提及;
-
特殊方法:小写和两个前导下划线,两个后置下划线
def __add__(self, other):
这种风格只应用于特殊函数,比如操作符重载等;
-
函数参数:小写和下划线,缺省值等号两边无空格
def connect(self, user=None):
6.5类
- 类总是使用驼峰格式命名,即所有单词首字母大写其余字母小写。类名应该简明,精确,并足以从中理解类所完成的工作。常见的一个方法是使用表示其类型或者特性的后缀,例如:
SQLEngine``MimeTypes
- 对于基类而言,可以使用一个 Base 前缀
BaseCookie
7编码建议
7.1比较和判空
-
尽可能使用
is
,is not
取代==
,比如 if x is None 要优于 if x == None -
用
is not
代替not ... is
,前者的可读性更好# YES if foo is not None # NO if not foo is None
-
对序列(字符串、列表、元组)判空,有如下规则:
# YES if not seq: pass if seq:
-
不要使用 == 进行布尔比较
# YES if greeting: pass # NO if greeting == True: pass
-
使用 startswith() 和 endswith() 代替切片进行序列前缀或后缀的检查
# YES if foo.startswith("bar"): pass # NO if foo[:3] == "bar":
-
使用 isinstance() 代替 type() 进行类型的比较
# YES if isinstance(obj, int): pass # NO if type(obj) is type(1): pass
7.2字符串处理
-
合理选择 % 操作符或者格式化方法格式化字符串;
-
避免在循环中使用 + 和 += 操作符来累加字符串;
由于字符串是不可变的,这样做会创建不必要的临时对象,并且导致二次方而不是线性的运行时间;可以将每个子字符串加入列表,然后在循环结束后用.join连接列表。
7.3其他
-
捕获异常时尽量指明具体异常,而不是空
except:
子句;空 “except:” 子句会捕捉 SystemExit 和 KeyboardInterrupt 异常,难以用 Control-C 中断程序,并可掩盖其他问题。如果 你捕捉信号错误之外所有的异常,使用 “except Exception”。
空 “except:” 子句适用的情况两种情况:
- 打印出或记录了traceback,至少让用户将知道已发生错误
- 代码需要做一些清理工作,并用 raise转发了异常。这样try…finally可以捕捉到它
-
函数或者方法在没有返回时要明确返回None。
# YES: def foo(x): if x >= 0: return math.sqrt(x) else: return None def bar(x): if x < 0: return None return math.sqrt(x) # NO def foo(x): if x >= 0: return math.sqrt(x) def bar(x): if x < 0: return return math.sqrt(x)