0%

13

自动摘要: 代码类型注释 你可以根据PEP484来对python3代码进行注释,并使用诸如[pytyp ……..

代码类型注释

你可以根据 PEP-484 来对 python3 代码进行注释,并使用诸如 pytype 之类的类型检查工具来检查代码.类型注释既可以写在源码,也可以写在 pyi 中.推荐尽量写在源码里,对于第三方扩展包,可以写在pyi文件里.

定义:用于函数参数和返回值的类型注释:

1
def func(a: int) -> List[int]:

也可以使用 PEP-526 中的语法来声明变量类型:

1
a: SomeType = some_func()

在必须支持老版本 python 运行的代码中则可以这样注释:

1
a = some_func() #type: SomeType

优点:可以提高代码可读性和可维护性.同时一些类型检查器可以帮您提早发现一些运行时错误,并降低您使用大威力特性的必要.

缺点:必须时常更新类型声明.过时的类型声明可能会误导您.使用类型检查器会抑制您使用大威力特性.

结论:强烈推荐您在更新代码时使用 python 类型分析.在添加或修改公共API时使用类型注释,在最终构建整个项目前使用 pytype 来进行检查.由于静态分析对于 python 来说还不够成熟,因此可能会出现一些副作用(例如错误推断的类型)可能会阻碍项目的部署.在这种情况下,建议作者添加一个 TODO 注释或者链接,来描述当前构建文件或是代码本身中使用类型注释导致的问题.

(译者注: 代码类型注释在帮助IDE或是vim等进行补全倒是很有效)

注释

确保对模块, 函数, 方法和行内注释使用正确的风格

文档字符串Python有一种独一无二的的注释方式: 使用文档字符串. 文档字符串是包, 模块, 类或函数里的第一个语句. 这些字符串可以通过对象的 __doc__ 成员被自动提取, 并且被pydoc所用. (你可以在你的模块上运行pydoc试一把, 看看它长什么样). 我们对文档字符串的惯例是使用三重双引号”””( PEP-257 ). 一个文档字符串应该这样组织: 首先是一行以句号, 问号或惊叹号结尾的概述(或者该文档字符串单纯只有一行). 接着是一个空行. 接着是文档字符串剩下的部分, 它应该与文档字符串的第一行的第一个引号对齐. 下面有更多文档字符串的格式化规范.

模块每个文件应该包含一个许可样板. 根据项目使用的许可(例如, Apache 2.0, BSD, LGPL, GPL), 选择合适的样板.其开头应是对模块内容和用法的描述.

1
2
3
4
5
6
7
8
9
10
11
12
"""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()
"""

函数和方法下文所指的函数,包括函数, 方法, 以及生成器.

一个函数必须要有文档字符串, 除非它满足以下条件:

  • 外部不可见
  • 非常短小
  • 简单明了

文档字符串应该包含函数做什么, 以及输入和输出的详细描述. 通常, 不应该描述”怎么做”, 除非是一些复杂的算法. 文档字符串应该提供足够的信息, 当别人编写代码调用该函数时, 他不需要看一行代码, 只要看文档字符串就可以了. 对于复杂的代码, 在代码旁边加注释会比使用文档字符串更有意义.

覆盖基类的子类方法应有一个类似 See base class 的简单注释来指引读者到基类方法的文档注释.若重载的子类方法和基类方法有很大不同,那么注释中应该指明这些信息.

关于函数的几个方面应该在特定的小节中进行描述记录, 这几个方面如下文所述. 每节应该以一个标题行开始. 标题行以冒号结尾. 除标题行外, 节的其他内容应被缩进2个空格.

Args:列出每个参数的名字, 并在名字后使用一个冒号和一个空格, 分隔对该参数的描述.如果描述太长超过了单行80字符,使用2或者4个空格的悬挂缩进(与文件其他部分保持一致).描述应该包括所需的类型和含义.如果一个函数接受foo(可变长度参数列表)或者bar (任意关键字参数), 应该详细列出foo和bar.

Returns: (或者 Yields: 用于生成器)描述返回值的类型和语义. 如果函数返回None, 这一部分可以省略.

Raises:列出与接口有关的所有异常.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def fetch_smalltable_rows(table_handle: smalltable.Table,
keys: Sequence[Union[bytes, str]],
require_all_keys: bool = False,
) -> Mapping[bytes, Tuple[str]]:
"""Fetches rows from a Smalltable.

Retrieves rows pertaining to the given keys from the Table instance
represented by table_handle. String keys will be UTF-8 encoded.

Args:
table_handle: An open smalltable.Table instance.
keys: A sequence of strings representing the key of each table
row to fetch. String keys will be UTF-8 encoded.
require_all_keys: Optional; If require_all_keys is True only
rows with values set for all keys will be returned.

Returns:
A dict mapping keys to the corresponding table row data
fetched. Each row is represented as a tuple of strings. For
example:

{b'Serak': ('Rigel VII', 'Preparer'),
b'Zim': ('Irk', 'Invader'),
b'Lrrr': ('Omicron Persei 8', 'Emperor')}

Returned keys are always bytes. If a key from the keys argument is
missing from the dictionary, then that row was not found in the
table (and require_all_keys must have been False).

Raises:
IOError: An error occurred accessing the smalltable.
"""

Args: 上进行换行也是可以的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def fetch_smalltable_rows(table_handle: smalltable.Table,
keys: Sequence[Union[bytes, str]],
require_all_keys: bool = False,
) -> Mapping[bytes, Tuple[str]]:
"""Fetches rows from a Smalltable.

Retrieves rows pertaining to the given keys from the Table instance
represented by table_handle. String keys will be UTF-8 encoded.

Args:
table_handle:
An open smalltable.Table instance.
keys:
A sequence of strings representing the key of each table row to
fetch. String keys will be UTF-8 encoded.
require_all_keys:
Optional; If require_all_keys is True only rows with values set
for all keys will be returned.

Returns:
A dict mapping keys to the corresponding table row data
fetched. Each row is represented as a tuple of strings. For
example:

{b'Serak': ('Rigel VII', 'Preparer'),
b'Zim': ('Irk', 'Invader'),
b'Lrrr': ('Omicron Persei 8', 'Emperor')}

Returned keys are always bytes. If a key from the keys argument is
missing from the dictionary, then that row was not found in the
table (and require_all_keys must have been False).

Raises:
IOError: An error occurred accessing the smalltable.
"""

类应该在其定义下有一个用于描述该类的文档字符串. 如果你的类有公共属性(Attributes), 那么文档中应该有一个属性(Attributes)段. 并且应该遵守和函数参数相同的格式.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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."""

块注释和行注释

最需要写注释的是代码中那些技巧性的部分. 如果你在下次 代码审查 的时候必须解释一下, 那么你应该现在就给它写注释. 对于复杂的操作, 应该在其操作开始前写上若干行注释. 对于不是一目了然的代码, 应在其行尾添加注释.

1
2
3
4
5
6
# 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个空格.

另一方面, 绝不要描述代码. 假设阅读代码的人比你更懂Python, 他只是不知道你的代码要做什么.

1
2
# BAD COMMENT: Now go through the b array and make sure whenever i occurs
# the next element is i+1

全局变量

避免全局变量

定义:定义在模块级的变量.

优点:偶尔有用.

缺点:导入时可能改变模块行为, 因为导入模块时会对模块级变量赋值.

结论:避免使用全局变量.鼓励使用模块级的常量,例如 MAX_HOLY_HANDGRENADE_COUNT = 3.注意常量命名必须全部大写,用 _ 分隔.具体参见 命名规则若必须要使用全局变量,应在模块内声明全局变量,并在名称前 _ 使之成为模块内部变量.外部访问必须通过模块级的公共函数.具体参见 命名规则

嵌套/局部/内部类或函数

使用内部类或者嵌套函数可以用来覆盖某些局部变量.

定义:类可以定义在方法, 函数或者类中. 函数可以定义在方法或函数中. 封闭区间中定义的变量对嵌套函数是只读的. (译者注:即内嵌函数可以读外部函数中定义的变量,但是无法改写,除非使用 nonlocal)

优点:允许定义仅用于有效范围的工具类和函数.在装饰器中比较常用.

缺点:嵌套类或局部类的实例不能序列化(pickled). 内嵌的函数和类无法直接测试.同时内嵌函数和类会使外部函数的可读性变差.

结论:使用内部类或者内嵌函数可以忽视一些警告.但是应该避免使用内嵌函数或类,除非是想覆盖某些值.若想对模块的用户隐藏某个函数,不要采用嵌套它来隐藏,应该在需要被隐藏的方法的模块级名称加 _ 前缀,这样它依然是可以被测试的.

命名

模块名写法: module_name ;包名写法: package_name ;类名: ClassName ;方法名: method_name ;异常名: ExceptionName ;函数名: function_name ;全局常量名: GLOBAL_CONSTANT_NAME ;全局变量名: global_var_name ;实例名: instance_var_name ;函数参数名: function_parameter_name ;局部变量名: local_var_name .

函数名,变量名和文件名应该是描述性的,尽量避免缩写,特别要避免使用非项目人员不清楚难以理解的缩写,不要通过删除单词中的字母来进行缩写.

始终使用 .py 作为文件后缀名,不要用破折号.

应该避免的名称

  • 单字符名称, 除了计数器和迭代器,作为 try/except 中异常声明的 e,作为 with 语句中文件句柄的 f.
  • 包/模块名中的连字符(-)
  • 双下划线开头并结尾的名称(Python保留, 例如init)

命名约定

  • 所谓”内部(Internal)”表示仅模块内可用, 或者, 在类内是保护或私有的.
  • 用单下划线(_)开头表示模块变量或函数是protected的(使用from module import *时不会包含).
  • 用双下划线(__)开头的实例变量或方法表示类内私有.
  • 将相关的类和顶级函数放在同一个模块里. 不像Java, 没必要限制一个类一个模块.
  • 对类名使用大写字母开头的单词(如CapWords, 即Pascal风格), 但是模块名应该用小写加下划线的方式(如lower_with_under.py). 尽管已经有很多现存的模块使用类似于CapWords.py这样的命名, 但现在已经不鼓励这样做, 因为如果模块名碰巧和类名一致, 这会让人困扰.

循环

大小循环问题,小循环在外,大循环在内

1
2
3
4
5
6
7
8
9
>>> import time
>>> total = 1
>>> time1 = time.time()
>>> for i in range(10):
... for j in range(10000000):
... total *= i + j
>>> time2 = time.time()
>>> print(time2 - time1)
168.1079998016

VS

1
2
3
4
5
6
7
8
9
>>> import time
>>> total = 1
>>> time3 = time.time()
>>> for i in range(1000000):
... for j in range(10):
... total *= i + j
>>> time4 = time.time()
>>> print(time4 - time3)
186.583000183

上面两种情形,同样遍历两次for循环,第一种方式无论耗时,性能都要比第二种好的多。 原因就是第二种方式,CPU要不停的切换,切换是需要耗时的,而且第二种方式从美观来说,是不如第一种。

在循环的时候,最好使用xrange来替代range

range()函数再遍历的时候会一次性列表都获取出来,xrange()会一个个获取,如果数据量小时,区别很小,如果数据量特别大时,差别会很大!

1
2
3
4
5
6
7
8
9
10
11
import time

total = 1
time1 = time.time()
for i in xrange(10):
for j in xrange(100000000):
total *= i + j
time2 = time.time()
print(time2 - time1)
>>> # 使用xrange函数耗时: 125秒
125.180000067

VS

1
2
3
4
5
6
7
8
9
10
11
import time

total = 1
time1 = time.time()
for i in range(10):
for j in range(100000000):
total *= i + j
time2 = time.time()
print(time2 - time1)
>>> # 使用range函数耗时: 167秒
167.417999983

如果for循环有条件判断,可以将条件放到for循环外

上面的这一条主要的安装Python的代码规范来说的。按照PEP8的规范无疑更推荐这种:

1
2
3
4
5
6
>>> if OS_TYPE = "win32":
>>> for i in range(len(ONE_VAR)):
>>> ......
>>> else:
>>> for j in range(len(TWO_VAR)):
>>> ......

欢迎关注我的其它发布渠道