首页 > temp > 简明python教程 >
-
Python 代码风格指南谷歌版(2)
可能会导致令人迷惑的bug例如这个基于PEP-0227的例子.
i = 4
def foo(x):
def bar():
print(i, end='')
# ...
# A bunch of code here
# ...
for i in x: # Ah, i *is* local to foo, so this is what bar sees i对于foo来说是局部变量,所以在这里就是bar函数所获取的值
print(i, end='')
bar()
所以foo([1, 2, 3])会打印1 2 3 3而非1 2 3 4.
2.16.4 建议
可以使用
2.17 函数和方法装饰器
在明显有好处时,谨慎明智的使用,避免@staticmethod,控制使用@classmethod
2.17.1 定义
函数和方法装饰器(也就是@记号).一个常见的装饰器是@property,用于将普通方法转换成动态计算属性.然而装饰器语法也允许用户定义装饰器,尤其对于一些函数my_decorator如下:
class C(object):
@my_decorator
def method(self):
# method body ...
是等效于
class C(object):
def method(self):
# method body ...
method = my_decorator(method)
2.17.2 Pros
能够优雅的对方法进行某种转换,而该转换可能减少一些重复代码并保持不变性等等.
2.17.3 Cons
装饰器可以对函数的参数和返回值任意操作,导致非常隐形的操作行为.此外,装饰器在import的时候就被执行,装饰器代码的实效可能非常难恢复.
2.17.4 建议
在有明显好处的地方谨慎地使用装饰器.装饰器应该和函数遵守相同的import和命名指导规则.装饰器的文档应该清晰地声明该函数为装饰器函数.并且要为装饰器函数编写单元测试.
避免装饰器自身对外部的依赖,(如不要依赖于文件,socket,数据库连接等等),这是由于在装饰器运行的时候(在import时,可能从pydoc或其他工具中)这些外部依赖可能不可用.一个被传入有效参数并调用的装饰器应该(尽可能)保证在任何情况下都可用.
装饰器是一种特殊的"顶级代码",参见main
永远不要使用@staticmethod,除非不得不整合一个API到一个已有的库,应该写一个模块等级的函数.
只在写一个命名的构造器或者一个类特定的,修改必要的全局状态(例如进程缓存等)的流程时使用@classmethod.
2.18 线程
不要依赖于内建类型的原子性
尽管Python内置数据类型例如字典等似乎有原子性操作,仍有一些罕见情况下,他们是非原子的(比如,如果hash__或者__eq被实现为Python方法),就不应该依赖于这些类型的原子性.也不应该依赖于原子变量赋值(因为这依赖于字典)
优先使用Queue模块的Queue类来作为线程之间通讯数据的方式.此外,要是用threading模块和其locking primitives(锁原语).了解条件变量的合理用法以便于使用threading.Condition而非使用更低级的锁.
2.19 过于强大的特性
尽量避免使用
2.19.1 定义
Python是一种非常灵活的语言并且提供了很多新奇的特性,诸如定制元类,访问字节码,动态编译,动态继承,对象父类重定义,import hacks,反射(例如一些对于getattr()的应用),系统内置的修改等等.
2.19.2 Pros
这些是非常强大的语言特性,可以让程序更紧凑
2.19.3 Cons
使用这些新特性是很诱人的.但是并不绝对必要,它们很难读很难理解.也很难debug那些在底层使用了不常见的特性的代码.对于原作者而言可能不是这样,但是再次看代码的时候,可能比更长但是更直接的代码要难.
2.19.4 定义
避免在代码中使用这些特性.
内部使用这些特性的标准库和类是可以使用的(例如abc.ABCMeta,collections.namedtuple,和enum)
2.20 新版本Python: Python3 和从__future__import
Python3已经可用了(译者:目前Python2已经不受支持了),尽管不是每个项目都准备好使用Python3,所有的代码应该兼容Python3并且在可能的情况下在Python3的环境下测试.
2.20.1 定义
Python3是Python的鲜明改变,当已有代码经常是Python2.7写成的,有一些简单可以做的事情来让代码对于其倾向更简明,因而可以让代码更好地在Python3下运行不用调整.
2.20.2 Pros
在考虑Python3同时写代码更清晰也更容易在Python3环境下运行(只要所有依赖已就绪).
2.20.3 Cons
一些人会认为默认样板有些丑,import实际不需要的特性到模块中是不常见的.
2.20.4 建议
from future imports
鼓励使用from future import语句.所有新代码都应该包含下述代码,而现有代码应该被更新以尽可能兼容:
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
如果你不太熟悉这些,详细阅读这些:绝对import,新的/除法行为,和print函数
请勿省略或移除这些import,即使在模块中他们没有在使用,除非代码只用于Python3.最好总是在所有的文档中都有从future的import,来保证不会在有人使用在后续编辑时遗忘.
有其他的from __future__import语句,看喜好使用.我们的建议中不包含unicode_literals因为其并无明显优势,这是由于隐式默认的编码转换导致其在Python2.7内很多地方,必要时,大多数代码最好显式的使用b''和u''btyes和unicode字符串表示.(译者:这段翻译可能不准确)
The six, future, or past libraries
当项目需要支持Python2和3时,根据需求使用six,future和past.
2.21 带有类型注释的代码
可以根据PEP-484对Python3代码进行类型注释,并且在build时用类型检查工具例如pytype进行类型检查.
类型注释可以在源码中或stub pyi file中.只要可能,注释就应写在源代码中.对于第三方或拓展模块使用pyi文件.
2.21.1 定义
类型注释(也称为"类型提示")是用于函数或方法参数和返回值的:
def func(a: int) -> List[int]:
你也可以声明用一个单独的注释来声明变量的类型:
a = SomeFunc() # type: SomeType
2.21.2 Pros
类型注释提升代码的可读性和可维护性,类型检查会将很多运行错误转化为构建错误,也减少了使用过于强力特性的能力.
2.21.3 Cons
需要不断更新类型声明,对于认为有效的代码可能会报类型错误,使用类型检查可能减少使用过于强力特性的能力.
2.21.4 建议
强烈鼓励在更新代码的时候进行Python类型分析.在对公共API进行补充和修改时,包括python类型声明并通过构建系统中的pytype进行检查.对Python来说静态类型检查比较新,我们承认,一些意料外的副作用(例如错误推断的类型)可能拒绝一些项目的使用.这种情况下,鼓励作者适当地增加一个带有TODO或到bug描述当前不接搜的类型注释的链接到BUILD文件或者在代码内.
3 Python代码风格规范
3.1 分好
不要在行尾加分号,也不要用分号把两行语句合并到一行
3.2 行长度
最大行长度是80个字符
超出80字符的明确例外:
-
长import
-
注释中的:URL,路径,flags等
-
不包含空格不方便分行的模块级别的长字符串常量
-
pylint的diable注释使用(如# pylint: disable=invalid-name)
不要使用反斜杠连接,除非对于需要三层或以上的上下文管理器with语句
利用Python的implicit line joining inside parentheses, brackets and braces(隐式行连接方法--括号连接,包括(), [], {}).如果必要的话,也可在表达式外面额外添加一对括号.
Yes:
foo_bar(self, width, height, color='black', design=None, x='foo',
emphasis=None, highlight=0)
if (width == 0 and height == 0 and
color == 'red' and emphasis == 'strong'):
当字符串不能在一行内完成时,使用括号来隐式连接行:
x = ('This will build a very long long '
'long long long long long long string')
在注释内,如有必要,将长URL放在其本行内:
Yes:
# See details at
# http://www.example.com/us/developer/documentation/api/content/v2.0/csv_file_name_extension_full_specification.html
No:
# See details at
# http://www.example.com/us/developer/documentation/api/content/\
# v2.0/csv_file_name_extension_full_specification.html
在定义一个表达式超过三行或更多的with语句时,可以使用反斜杠来分行.对于两行表达式,使用嵌套with语句:
Yes:
with very_long_first_expression_function() as spam, \
very_long_second_expression_function() as beans, \
third_thing() as eggs:
place_order(eggs, beans, spam, beans)
with very_long_first_expression_function() as spam:
with very_long_second_expression_function() as beans:
place_order(beans, spam)
No:
with VeryLongFirstExpressionFunction() as spam, \
VeryLongSecondExpressionFunction() as beans:
PlaceOrder(eggs, beans, spam, beans)
注意上述例子中的缩进,具体参看缩进
在其他一行超过80字符的情况下,而且yapf自动格式工具也不能使分行符合要求时,允许超过80字符限制.
3.3 括号
括号合理使用
尽管不必要,但是可以在元组外加括号.再返回语句或者条件语句中不要使用括号,除非是用于隐式的连接行或者指示元组.
Yes:
if foo:
bar()
while x:
x = bar()
if x and y:
bar()
if not x:
bar()
# For a 1 item tuple the ()s are more visually obvious than the comma.
onesie = (foo,)
return foo
return spam, beans
return (spam, beans)
for (x, y) in dict.items(): ...
No:
if (x):
bar()
if not(x):
bar()
return (foo)
3.4 缩进
缩进用4个空格
缩进代码段不要使用制表符,或者混用制表符和空格.如果连接多行,多行应垂直对齐,或者再次4空格缩进(这个情况下首行括号后应该不包含代码).
Yes:
# Aligned with opening delimiter
# 和opening delimiter对齐(译者理解是分隔符的入口,例如三种括号,字符串引号等)
foo = long_function_name(var_one, var_two,
var_three, var_four)
meal = (spam,
beans)
# Aligned with opening delimiter in a dictionary
foo = {
long_dictionary_key: value1 +
value2,
...
}
# 4-space hanging indent; nothing on first line
# 缩进4个空格,首行括号后无内容
foo = long_function_name(
var_one, var_two, var_three,
var_four)
meal = (
spam,
beans)
# 4-space hanging indent in a dictionary
foo = {
long_dictionary_key:
long_dictionary_value,
...
}
No:
# Stuff on first line forbidden
# 首行不允许有内容
foo = long_function_name(var_one, var_two,
var_three, var_four)
meal = (spam,
beans)
# 2-space hanging indent forbidden
foo = long_function_name(
var_one, var_two, var_three,
var_four)
# No hanging indent in a dictionary
foo = {
long_dictionary_key:
long_dictionary_value,
...
}
3.4.1 关于尾后逗号
关于在一序列元素中的尾号逗号,只推荐在容器结束符号],)或者}和最后元素不在同一行时使用.尾后逗号的存在也被用作我们Python代码自动格式化工具yapf的提示,在,最后元素之后出现的时候来自动调整容器元素到每行一个元素.
Yes:
golomb3 = [0, 1, 3]
golomb4 = [
0,
1,
4,
6,
]
No:
golomb4 = [
0,
1,
4,
6
]
3.5 空行
在顶级定义(函数或类)之间要间隔两行.在方法定义之间以及class所在行与第一个方法之间要空一行,def行后无空行,在函数或方法内你认为合适地方可以使用单空行.
3.6 空格
遵守标准的空格和标点排版规则.
括号(),[],{}内部不要多余的空格.
Yes:
spam(ham[1], {eggs: 2}, [])
No:
spam( ham[ 1 ], { eggs: 2 }, [ ] )
逗号、分号、冒号前不要空格,但是在后面要加空格,除非是在行尾.
Yes:
if x == 4:
print(x, y)
x, y = y, x
No:
if x == 4 :
print(x , y)
x , y = y , x
在函数调用括号的前,索引切片括号前都不加空格.
Yes:
spam(1)
dict['key'] = list[index]
No:
spam (1)
dict ['key'] = list [index]
行尾不要加空格.
在赋值(=),比较(==,<,>,!=,<>,<=,>=,in,not in,is,is not),布尔符号(and,or,not)前后都加空格.视情况在算术运算符(+,-,,/,//,%,*,@),前后加空格
Yes:
x == 1
No:
x<1
在关键字名参数传递或定义默认参数值的时候不要在=前后加空格,只有一个例外:当类型注释存在时在定义默认参数值时=前后加空格
Yes:
def complex(real, imag=0.0): return Magic(r=real, i=imag)
def complex(real, imag: float = 0.0): return Magic(r=real, i=imag)
No:
def complex(real, imag = 0.0): return Magic(r = real, i = imag)
def complex(real, imag: float=0.0): return Magic(r = real, i = imag)
不要用空格来做无必要的对齐,因为这会在维护时带来不必哟的负担(对于:.#,=等等).
Yes:
foo = 1000 # comment
long_name = 2 # comment that should not be aligned
dictionary = {
'foo': 1,
'long_name': 2,
}
No:
foo = 1000 # comment
long_name = 2 # comment that should not be aligned
dictionary = {
'foo' : 1,
'long_name': 2,
}
3.7 Shebang
大部分.py文件不需要从#!行来开始.根据PEP-394,程序的主文件应该以#!/usr/bin/python2或#!/usr/bin/python3起始
这行被用于帮助内核找到Python解释器,但是在导入模块时会被Python忽略/只在会被直接运行的文件里有必要写.
3.8 注释和文档字符串
确保使用正确的模块,函数,方法的文档字符串和行内注释.
3.8.1 文档字符串
Python使用文档字符串来为代码生成文档.文档字符串是包,模块,类活函数的首个语句.这些字符串能够自动被doc成员方法提取并且被pydoc使用.(尝试在你的模块上运行pydoc来看看具体是什么).文档字符串使用三重双引号"""(根据PEP-257).文档字符串应该这样组织:一行总结(或整个文档字符串只有一行)并以句号,问好或感叹号结尾.随后是一行空行,随后是文档字符串,并与第一行的首个引号位置相对齐.更多具体格式规范如下.
3.8.2 模块
每个文件都应包含许可模板.选择合适的许可模板用于项目(例如
Apache 2.0,BSD,LGPL,GPL)
文档应该以文档字符串开头,并描述模块的内容和使用方法.
"""A one line summary of the module or program, terminated by a period.
Leave one blank line. The rest of this docstring should contain an
overall description of the module or program. Optionally, it may also
contain a brief description of exported classes and functions and/or usage
examples.
Typical usage example:
foo = ClassFoo()
bar = foo.FunctionBar()
"""
3.8.3 函数和方法
在本节,"函数"所指包括方法,函数或者生成器.
函数应有文档字符串,除非符合以下所有条件:
-
外部不可见
-
非常短
-
简明
文档字符串应该包含足够的信息以在无需阅读函数代码的情况下调用函数.文档字符串应该是叙事体("""Fetches rows from a Bigtable.""")的而非命令式的("""Fetch rows from a Bigtable."""),除了@property(应与attribute使用同样的风格).文档字符串应描述函数的调用语法和其意义,而非实现.对比较有技巧的地方,在代码中使用注释更合适.
覆写了基类的方法可有简单的文档字符串向读者指示被覆写方法的文档字符串例如"""See base class.""".这是因为没必要在很多地方重复已经在基类的文档字符串中存在的文档.不过如果覆写的方法行为实际上与被覆写方法不一致,或者需要提供细节(例如文档中表明额外的副作用),覆写方法的文档字符串至少要提供这些差别.
一个函数的不同方面应该在特定对应的分节里写入文档,这些分节如下.每一节都由以冒号结尾的一行开始, 每一节除了首行外,都应该以2或4个空格缩进并在整个文档内保持一致(译者建议4个空格以维持整体一致).如果函数名和签名足够给出足够信息并且能够刚好被一行文档字符串所描述,那么可以忽略这些节.
Args:
列出每个参数的名字.名字后应有为冒号和空格,后跟描述.如果描述太长不能够在80字符的单行内完成.那么分行并缩进2或4个空格且与全文档一致(译者同样建议4个空格)
描述应该包含参数所要求的类型,如果代码不包含类型注释的话.如果函数容许foo(不定长度参数列表)或bar(任意关键字参数).那么就应该在文档字符串中列举为foo和bar.
Returns:(或对于生成器是Yields:)
描述返回值的类型和含义.如果函数至少返回None,这一小节不需要.如果文档字符串以Returns或者Yields开头(例如"""Returns row from Bigtable as a tuple of strings.""")或首句足够描述返回值的情况下这一节可忽略.
Raises:
列出所有和接口相关的异常.对于违反文档要求而抛出的异常不应列出.(因为这会矛盾地使得违反接口要求的行为成为接口的一部分)
def fetch_bigtable_rows(big_table, keys, other_silly_variable=None):
"""Fetches rows from a Bigtable.
Retrieves rows pertaining to the given keys from the Table instance
represented by big_table. Silly things may happen if
other_silly_variable is not None.
Args:
big_table: An open Bigtable Table instance.
keys: A sequence of strings representing the key of each table row
to fetch.
other_silly_variable: Another optional variable, that has a much
longer name than the other args, and which does nothing.
Returns:
A dict mapping keys to the corresponding table row data
fetched. Each row is represented as a tuple of strings. For
example:
{'Serak': ('Rigel VII', 'Preparer'),
'Zim': ('Irk', 'Invader'),
'Lrrr': ('Omicron Persei 8', 'Emperor')}
If a key from the keys argument is missing from the dictionary,
then that row was not found in the table.
Raises:
IOError: An error occurred accessing the bigtable.Table object.
"""
3.8.4 类
类定义下一行应为描述这个类的文档字符串.如果类有公共属性,应该在文档字符串中的Attributes节中注明,并且和函数的Args一节风格统一.
class SampleClass(object):
"""Summary of class here.
Longer class information....
Longer class information....
Attributes:
likes_spam: A boolean indicating if we like SPAM or not.
eggs: An integer count of the eggs we have laid.
"""
def __init__(self, likes_spam=False):
"""Inits SampleClass with blah."""
self.likes_spam = likes_spam
self.eggs = 0
def public_method(self):
"""Performs operation blah."""
3.8.5 块注释和行注释
最后要在代码中注释的地方是代码技巧性的部分.如果你将要在下次code review中揭示代码.应该现在就添加注释.在复杂操作开始前,注释几行.对于不够明晰的代码在行尾注释.
# We use a weighted dictionary search to find out where i is in
# the array. We extrapolate position based on the largest num
# in the array and the array size and then do binary search to
# get the exact number.
if i & (i-1) == 0: # True if i is 0 or a power of 2.
为了提升易读性,行注释应该至少在代码2个空格后,并以#后接至少1个空格开始注释部分.
另外,不要描述代码,假定阅读代码的人比你更精通Python(他只是不知道你试图做什么).
3.8.6 标点,拼写和语法
注意标点,拼写和语法,写得好的注释要比写得差的好读.
注释应当是和叙事性文本一样可读,并具有合适的大小写和标点.在许多情况下,完整的句子要比破碎的句子更可读.更简短的注释如行尾的注释有时会不太正式,但是应该全篇保持风格一致.
尽管被代码审核人员指出在应该使用分号的地方使用了逗号是很令人沮丧的,将源代码维护在高度清楚可读的程度是很重要的.合适的标点,拼写和语法能够帮助达到这个目标.
3.9 类
如果类并非从其他基类继承而来,那么就要明确是从object继承而来,即便内嵌类也是如此.
Yes:
class SampleClass(object):
pass
class OuterClass(object):
class InnerClass(object):
pass
class ChildClass(ParentClass):
"""Explicitly inherits from another class already."""
No:
class SampleClass:
pass
class OuterClass:
class InnerClass:
pass
从object类继承保证了属性能够在Python2正确运行并且保护代码在Python3下出现潜在的不兼容.这样也定义了object包括new__,_init_,_delattr_,_getattribute_,_setattr_,_hash_,_repr_,和__str等默认特殊方法的实现.
3.10 字符串
使用format或%来格式化字符串,即使参数都是字符串对象,也要考虑使用+还是%及format.
Yes:
x = a + b
x = '%s, %s!' % (imperative, expletive)
x = '{}, {}'.format(first, second)
x = 'name: %s; score: %d' % (name, n)
x = 'name: {}; score: {}'.format(name, n)
x = f'name: {name}; score: {n}' # Python 3.6+
No:
employee_table = '<table>'
for last_name, first_name in employee_list:
employee_table += '<tr><td>%s, %s</td></tr>' % (last_name, first_name)
employee_table += '</table>'
避免使用+和+=操作符来在循环内累加字符串,因为字符串是不可变对象.这会造成不必要的临时变量导致运行时间以四次方增长而非线性增长.应将每个字符串都记入一个列表并使用''.join来将列表在循环结束后连接(或将每个子字符串写入io.BytesIO缓存)
Yes:
items = ['<table>']
for last_name, first_name in employee_list:
items.append('<tr><td>%s, %s</td></tr>' % (last_name, first_name))
items.append('</table>')
employee_table = ''.join(items)
No:
employee_table = '<table>'
for last_name, first_name in employee_list:
employee_table += '<tr><td>%s, %s</td></tr>' % (last_name, first_name)
employee_table += '</table>'
在同一个文件内,字符串引号要一致,选择''或者""并且不要改变.对于需要避免\转义的时候,可以更改.
Yes:
Python('Why are you hiding your eyes?')
Gollum("I'm scared of lint errors.")
Narrator('"Good!" thought a happy Python reviewer.')
No:
Python("Why are you hiding your eyes?")
Gollum('The lint. It burns. It burns us.')
Gollum("Always the great lint. Watching. Watching.")
多行字符串多行字符串优先使用"""而非''',当且只当对所有非文档字符串的多行字符串都是用'''而且对正常字符串都使用'时才可使用三单引号.docstring不论如何必须使用"""
多行字符串和其余代码的缩进方式不一致.如果需要避免在字符串中插入额外的空格,要么使用单行字符串连接或者带有textwarp.dedent()的多行字符串来移除每行的起始空格.
No:
long_string = """This is pretty ugly.
Don't do this.
"""
Yes:
long_string = """This is fine if your use case can accept
extraneous leading spaces."""
long_string = ("And this is fine if you can not accept\n" +
"extraneous