基础的
Python
代码规范可以使用vscode
的代码格式化插件,一键格式化代码。多学习多总结让自己的代码更具“Pythonic”
。
前言
如果您问 Python
程序员最喜欢 Python
的什么,他们总会说是 Python
的高可读性。 事实上,高度的可读性是 Python
语言的设计核心。这基于这样的事实:代码的 阅读比编写更加频繁。Python 代码具有高可读性的其中一个原因是它的相对完整的代码风格指引和 “Pythonic”
的习语。
当一位富有经验的 Python 开发者(Pythonista)指出某段代码并不 “Pythonic”
时, 通常意味着这些代码并没有遵循通用的指导方针,也没有用最佳的(最可读的)方式 来表达意图。
在某些边缘情况下,Python 代码中并没有大家都认同的表达意图的最佳方式,但这些情况 都很少见。
Python 语言规范
pylint
pylint 是一个在 Python 源代码中查找 bug
的工具(包),由于 Python 的动态语言特性,有些警告可能不对,需要手动去关闭。对于 C/C++
这样的静态语言(less dynamic),代码的 bug
通常要通过编译器编译代码才能产生。
列表推导(List Comprehensions)
可以在简单情况下使用。
列表推导(list comprehensions
)与生成器表达式(generator expression
)提供了一种简洁高效的方式来创建列表和迭代器,而不必借助 map()
、filter()
、或者 lambda
高阶函数。
其适用于简单情况,每个部分应该单独置于一行:映射表达式,for
语句,过滤器表达式。禁止多重 for
语句或过滤器表达式。复杂情况下还是使用循环。示例代码如下。
Yes:
result = []
for x in range(10):
for y in range(5):
if x * y > 10:
result.append((x, y))
for x in xrange(5):
for y in xrange(5):
if x != y:
for z in xrange(5):
if y != z:
yield (x, y, z)
return ((x, complicated_transform(x))
for x in long_generator_function(parameter)
if x is not None)
squares = [x * x for x in range(10)]
eat(jelly_bean for jelly_bean in jelly_beans
if jelly_bean.color == 'black')
默认迭代器和操作符
如果类型支持,就使用默认迭代器和操作符。比如列表,字典及文件等。
默认操作符和迭代器简单高效,它们直接表达了操作, 没有额外的方法调用。使用默认操作符的函数是通用的,它可以用于支持该操作的任何类型。
如果类型支持,就使用默认迭代器和操作符,例如列表、字典和文件。内建类型也定义了迭代器方法。优先考虑这些方法,而不是那些返回列表的方法。当然,这样遍历容器时,你将不能修改容器。
Yes: for key in adict: ...
if key not in adict: ...
if obj in alist: ...
for line in afile: ...
for k, v in dict.iteritems(): ...
No: for key in adict.keys(): ...
if not adict.has_key(key): ...
for line in afile.readlines(): ...
生成器
按需使用生成器。
**定义:**所谓生成器函数,就是每当它执行一次生成 (yield)
语句,它就返回一个迭代器,这个迭代器生成一个值。生成值后,生成器函数的运行状态将被挂起,直到下一次生成。
Lambda (匿名)函数
适用于简单的单行函数。
对于常见的操作符,例如乘法操作符,使用 operator
模块中的函数以代替 lambda
函数。例如, 推荐使用 operator.mul(x,y)
, 而不是 lambda x, y: x * y
。示例如下。
from functools import reduce
import operator
def add(x, y):
return x + y
# 对列表所有元素进行求和
sum_ret1 = reduce(add, [1, 3, 5, 7, 9])
sum_ret2 = reduce(lambda x,y: x+y, [1, 3, 5, 7, 9])
sum_ret3 = reduce(lambda x,y: operator.add(x,y), [1, 3, 5, 7, 9])
print(sum_ret1, sum_ret2, sum_ret3)
#### 程序运行结果 ####
# 25 25 25
operator
模块提供了一套与Python
的内置运算符对应的高效率函数。例如,operator.mul(x, y)
与表达式x*y
相同。
条件表达式
适用于单行函数。
条件表达式是对于 if
语句的一种更为简短的句法规则。例如: x = 1 if cond else 0
。
默认参数值
鼓励使用,其适用于大部分函数情况。
我们可以在函数参数列表的最后指定一个或多个变量的值(必选参数在前,默认参数在后),例如,def foo(a, b = 0):
。如果调用 foo
时只带一个参数,则 b
被设为 0
。如果带两个参数,则 b
的值等于第二个参数。
关键字参数
扩展函数的功能。
def resnext101_32x8d(pretrained=False, progress=True, **kwargs):
r"""ResNeXt-101 32x8d model from
`"Aggregated Residual Transformation for Deep Neural Networks" <https://arxiv.org/pdf/1611.05431.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
kwargs['groups'] = 32
kwargs['width_per_group'] = 8
return _resnet('resnext101_32x8d', Bottleneck, [3, 4, 23, 3],
pretrained, progress, **kwargs)
属性(properties)
访问和设置类的数据成员时,我们通常会使用简单、轻量级的访问和设置函数,建议用属性(
properties
)来代替它们。
类
类的设计尽量遵守 SOLID
原则,这个需要个人大量的实践经验和理解面向对象思想。
遵循的约定
检查变量是否等于常量
不需要比较一个值是 True
、False
、None
或者 0
,可以仅仅把它放在 if
语句中判断。
糟糕的代码:
attr = None
if attr == True:
print('attr is True!')
if attr == None:
print('attr is None!')
优雅的代码:
if attr: # 检查值
print('condition is True!')
if not attr: # 做相反的检查
print('condition is False!')
if attr is None: # or, since None is considered false, explicitly check for it
print ('attr is None!')
维护列表的捷径
建议使用 Python
的高级函数来维护和更新 列表等数据结构,如 map/reduce
、filter
、sorted
函数。
修改原始列表可能产生的副作用
如果有其他变量引用原始列表,则修改它可能会有风险。但如果你真的想这样做,你可以使用切片赋值(slice assignment
) 。
# 修改原始列表的内容
sequence[::] = [value for value in sequence if value != x]
# 或者
sequence[::] = (value for value in sequence if value != x)
# 或者
sequence[::] = filter(lambda value: value != x, sequence)
在 Python 中,赋值永远不会创建新对象。如果两个或多个变量引用相同的列表,则修改其中一个变量意味着将修改所有变量,实例如下所示:
Python 风格规范
分号
不要在行尾加分号,也不要用分号将两条命令放在同一行。
行长度
建议每行代码不超过 80
个字符,但也有两个意外:
- 长的导入模块语句
- 注释里的
URL
实例代码:
# 建议换行
id_paramsM = {key: format(value, '0.2E') for (key, value) in id_params.items()}
# 较规范写法
id_paramsM = {key: format(value, '0.2E')
for (key, value) in id_params.items()}
括号
宁缺毋滥的使用括号。除非是用于实现行连接,否则不要在返回语句或条件语句中使用括号,不过在元组两边使用括号是可以的。
缩进
一定要遵守严格的缩进,使用 4 个空格来缩进代码,建议使用 vscode 代码格式化插件直接规范你的代码缩进。
绝对不要用 tab
,也不要 tab
和空格混用。对于行连接的情况,你应该要么垂直对齐换行的元素(见 行长度 部分的示例),或者使用 4
空格的悬挂式缩进(这时第一行不应该有参数)。
Yes: # Aligned with opening delimiter
foo = long_function_name(var_one, var_two,
var_three, var_four)
# Aligned with opening delimiter in a dictionary
foo = {
long_dictionary_key: value1 +
value2,
...
}
# 4-space hanging indent; nothing on first line
foo = long_function_name(
var_one, var_two, var_three,
var_four)
# 4-space hanging indent in a dictionary
foo = {
long_dictionary_key:
long_dictionary_value,
...
}
空行
顶级定义之间空两行,方法定义之间空一行。
顶级定义之间空两行,比如函数或者类定义。方法定义,类定义与第一个方法 __init__()
之间,都应该空一行。函数或方法中,某些地方要是你觉得合适, 就空一行(更方便阅读代码)。
空格
在二元操作符两边都加上一个空格,比如赋值(=
), 比较(==, <, >, !=, <>, <=, >=, 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)
No: def complex(real, imag = 0.0): return magic(r = real, i = imag)
Shebang
工程代码中的大部分
.py
文件不必以#!
作为文件的开始。根据 PEP-394,程序的main
文件应该以#!/usr/bin/python2
或者#!/usr/bin/python3
开始。
在计算机科学中, Shebang (也称为Hashbang)是一个由井号和叹号构成的字符串行(#!), 其出现在文本文件的第一行的前两个字符. 在文件中存在Shebang的情况下, 类Unix操作系统的程序载入器会分析Shebang后的内容, 将这些内容作为解释器指令, 并调用该指令, 并将载有Shebang的文件路径作为该解释器的参数. 例如, 以指令#!/bin/sh开头的文件在执行时会实际调用/bin/sh程序。
#!
先用于帮助内核找到 Python
解释器,但是在导入模块时,将会被忽略。因此只有被直接执行的文件中才有必要加入 #!
。
注释
确保对代码中的模块、函数、类(方法)和行内注释使用正确且同一的风格。
文档字符串
Python 有一种独一无二的的注释方式: 使用文档字符串. 文档字符串是包, 模块, 类或函数里的第一个语句. 这些字符串可以通过对象的 doc 成员被自动提取, 并且被pydoc所用. (你可以在你的模块上运行pydoc试一把, 看看它长什么样). 我们对文档字符串的惯例是使用三重双引号”””(PEP-257). 一个文档字符串应该这样组织: 首先是一行以句号, 问号或惊叹号结尾的概述(或者该文档字符串单纯只有一行). 接着是一个空行. 接着是文档字符串剩下的部分, 它应该与文档字符串的第一行的第一个引号对齐. 下面有更多文档字符串的格式化规范。