1 背景

Python是Google使用的主要动态语言。该样式指南列出了Python程序的注意事项。

为了帮助正确设置代码格式,所以为Vim创建了一个设置文件。对于Emacs用户,保持默认设置即可。

2 Python语言规则

2.1 Lint

对你的代码运行pylint

2.1.1 定义

pylint是用于在Python源代码中查找错误和样式问题的工具。它发现对于动态性较差的语言(例如C和C ++),通常由编译器发现这些问题。由于Python的动态特性,某些警告可能是不正确的。但是,虚假警告很少出现。

2.1.2 优点

可以捕获容易忽视的错误, 例如输入错误, 使用未赋值的变量等.

2.1.3 缺点

pylint并不完美。要利用它,我们有时需要:围绕它写;禁止其警告;对其进行改进。

2.1.4 结论 

确保pylint在代码上运行。

如果警告不适当,则禁止显示这些警告,这样就不会隐藏其他问题。要禁止显示警告,可以设置行级别的注释:

dict = 'something awful'  # Bad Idea... pylint: disable=redefined-builtin

pylint警告均以符号名(empty-docstring)标识。Google特定警告以开头g-。

如果从符号名称中看不到抑制的原因,请添加说明。

以这种方式进行抑制的优势在于,我们可以轻松地搜索抑制并重新进行抑制。

您可以pylint通过执行以下操作获取警告列表:

pylint --list-msgs

要获取有关特定消息的更多信息,请使用:

pylint --help-msg=C6409

相比较之前使用的pylint: disable-msg,现在推荐使用 pylint: disable.

可以通过删除函数开头的变量来抑制未使用的参数警告。始终包含一条注释,解释为什么要删除它。“未使用”就足够了。例如:

def viking_cafe_order(spam, beans, eggs=None):

    del beans, eggs  # Unused by vikings.

    return spam + spam + spam

抑制此警告的其他常见形式包括使用''uu'作为未使用参数的标识符,或在参数名称前面加上'unused_u',或将它们指派给''uu'。允许但不再鼓励这些形式。这些中断调用者按名称传递参数,而不强制实参实际上未使用。

2.2 导入

仅对包和模块使用导入

2.2.1 定义

从一个模块到另一个模块共享代码的可重用机制。

2.2.2 优点

命名空间管理约定很简单。每个标识符的来源以一致的方式表示;x.Obj表示对象Obj是在模块x中定义的。

2.2.3 缺点

模块名称仍然可能发生冲突。有些模块名称很长,不方便使用。

2.2.4 结论

使用import x导入包和模块。

使用from x import y,其中x是包前缀,y是没有前缀的模块名称。

如果要导入两个名为y的模块或y是一个不方便的长名称,请使用from x import y as z。

仅当z是标准缩写时才使用import y作为z(例如,np表示numpy)。

例如, 模块 sound.effects.echo 可以用如下方式导入:

from sound.effects import echo

...

echo.EchoFilter(input, output, delay=0.7, atten=4)

在导入中不要使用相对名称。即使模块在同一个包中,也要使用完整的包名。这有助于防止无意中导入包两次。

2.3 包

使用模块的完整路径名位置导入每个模块。

2.3.1 优点

避免由于模块搜索路径不是作者期望的模块名称冲突或导入错误。使查找模块更加容易。

2.3.2 缺点

由于必须复制程序包层次结构,因此使部署代码更加困难。

2.3.3 结论

所有的新代码都应该用完整包名来导入每个模块.应该像下面这样导入:

# Reference absl.flags in code with the complete name (verbose).

import absl.flags

from doctor.who import jodie

 

FLAGS = absl.flags.FLAGS

# Reference flags in code with just the module name (common).

from absl import flags

from doctor.who import jodie

 

FLAGS = flags.FLAGS

2.4 异常

允许异常,但必须谨慎使用。

2.4.1 定义

异常是一种打破常规的代码块控制流以处理错误或其他特殊情况的方法。

2.4.2 优点

正常操作代码的控制流程不会因错误处理代码而混乱。它还允许控制流在发生某种情况时跳过多个帧,例如,在一个步骤中从N个嵌套函数返回,而不必携带错误代码。

2.4.3 缺点

可能导致控制流程混乱。进行库调用时容易错过错误情况。

2.4.4 结论

异常必须符合某些条件:

引发像这样的异常:raise MyError('Error message')或raise MyError()。不要使用两个参数的形式(raise MyError, 'Error message')。

在合理的情况下,请使用内置的异常类。

模块或包应该定义自己的特定域的异常基类, 这个基类应该从内建的Exception类继承. 模块的异常基类应该叫做”Error”。

永远不要使用 except: 语句来捕获所有异常, 也不要捕获 Exception 或者 StandardError , 除非你打算重新触发该异常, 或者你已经在当前线程的最外层(记得还是要打印一条错误消息). 在异常这方面, Python非常宽容, except: 真的会捕获包括Python语法错误在内的任何错误. 使用 except: 很容易隐藏真正的bug.

尽量减少try/except块中的代码量. try块的体积越大, 期望之外的异常就越容易被触发. 这种情况下, try/except块将隐藏真正的错误.

使用finally子句来执行那些无论try块中有没有异常都应该被执行的代码. 这对于清理资源常常很有用, 例如关闭文件.

当捕获异常时, 使用 as 而不要用逗号.

2.5 全局变量

避免使用全局变量。

2.5.1 定义

在模块级别或作为类属性声明的变量。

2.5.2 优点

偶尔有用。

2.5.3 缺点

可能会在导入期间更改模块的行为,因为在首次导入模块时会完成对全局变量的分配。

2.5.4 结论

避免使用全局变量。

尽管它们在技术上是变量,但允许并鼓励使用模块级常量。例如:MAX_HOLY_HANDGRENADE_COUNT = 3。必须使用所有带下划线的大写字母来命名常量。请参阅下面的命名。

如果需要,全局变量应该在模块级别声明,并通过在名称前面加上‘_’使其成为模块的内部变量。外部访问必须通过公共模块级函数完成。

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

当用于关闭局部变量时,推荐使用嵌套的局部函数或类。

2.6.1 定义

可以在方法,函数或类内部定义类。可以在方法或函数内部定义函数。嵌套函数对包含在作用域中的变量具有只读访问权限。

2.6.2 优点

允许定义仅在非常有限的范围内使用的实用程序类和函数。

2.6.3 缺点

嵌套类或局部类的实例不能序列化(pickled).

2.6.4 结论

推荐使用.他们很好,但有一些警告。避免嵌套函数或类,除非关闭局部值。不要嵌套仅将其隐藏给模块用户的函数。而是在模块级别用_前缀它的名称,以便测试仍然可以访问它。

2.7 列表推导;生成器表达式

可以在简单情况下使用

2.7.1 定义

列表推导(list comprehensions)与生成器表达式(generator expression)提供了一种简洁高效的方式来创建列表和迭代器, 而不必借助map(), filter(), 或者lambda.

2.7.2 优点

简单的理解比其他字典,列表或集合创建方法更清晰,更简单。生成器表达式可以非常有效,因为它们完全避免了创建列表。

2.7.3 缺点

复杂的列表推导或生成器表达式可能很难阅读。

2.7.4 结论

适用于简单情况. 每个部分应该单独置于一行: 映射表达式, for语句, 过滤器表达式. 禁止多重for语句或过滤器表达式. 复杂情况下还是使用循环.

Yes:

  result = [mapping_expr for value in iterable if filter_expr]

 

  result = [{'key': value} for value in iterable

            if a_long_filter_expression(value)]

 

  result = [complicated_transform(x)

            for x in iterable if predicate(x)]

 

  descriptive_name = [

      transform({'key': key, 'value': value}, color='black')

      for key, value in generate_iterable(some_input)

      if complicated_condition_is_met(key, value)

  ]

 

  result = []

  for x in range(10):

      for y in range(5):

          if x * y > 10:

              result.append((x, y))

 

  return {x: complicated_transform(x)

          for x in long_generator_function(parameter)

          if x is not None}

 

  squares_generator = (x**2 for x in range(10))

 

  unique_names = {user.name for user in users if user is not None}

 

  eat(jelly_bean for jelly_bean in jelly_beans

      if jelly_bean.color == 'black')

No:

  result = [complicated_transform(

                x, some_argument=x+1)

            for x in iterable if predicate(x)]

 

  result = [(x, y) for x in range(10) for y in range(5) if x * y > 10]

 

  return ((x, y, z)

          for x in range(5)

          for y in range(5)

          if x != y

          for z in range(5)

          if y != z)

2.8 默认迭代器和操作符

对支持它们的类型使用默认的迭代器和操作符,例如列表,字典和文件。

2.8.1 定义

容器类型,如字典和列表,定义了默认的迭代器和关系测试操作符(“ in”和“ not in”)。

2.8.2 优点

默认的迭代器和操作符既简单又高效。它们直接表示操作,而无需额外的方法调用。使用默认操作符的函数是通用的。可以与支持该操作的任何类型一起使用。

2.8.3 缺点

无法通过读取方法名称(例如,has_key() 表示字典)来分辨对象的类型。不过这也可以说是一个优势。

2.8.4 结论

如果类型支持, 就使用默认迭代器和操作符, 例如列表, 字典和文件. 内建类型也定义了迭代器方法. 优先考虑这些方法, 而不是那些返回列表的方法. 当然,这样遍历容器时,你将不能修改容器.

Yes:  for key in adict: ...

      if key not in adict: ...

      if obj in alist: ...

      for line in afile: ...

      for k, v in adict.items(): ...

      for k, v in six.iteritems(adict): ...

No:   for key in adict.keys(): ...

      if not adict.has_key(key): ...

      for line in afile.readlines(): ...

      for k, v in dict.iteritems(): ...

2.9 生成器

按需使用生成器.

2.9.1 定义

生成器函数返回一个迭代器,该迭代器在每次执行yield语句时都会生成一个值。在产生一个值之后,生成器函数的运行时状态将被挂起,直到需要下一个值为止。

2.9.2 优点

代码更简单,因为每次调用都会保留局部变量的状态和控制流。生成器所使用的内存少于一次创建整个值列表的函数所需要的内存。

2.9.3 结论

在生成器函数的文档字符串中使用“ Yields:”而不是“ Returns:”。

2.10 Lambda函数

适用于单行函数

2.10.1 定义

Lambda在表达式(而不是语句)中定义匿名函数。它们通常用于为诸如map()和filter()这样的高阶函数定义回调或操作符。

2.10.2 优点

方便。

2.10.3 缺点

比本地函数更难阅读和调试。缺少函数名意味着堆栈跟踪更难以理解。由于函数只能包含一个表达式,因此可表达性受到限制。

2.10.4 结论

适用于单行函数.如果lambda函数中的代码长度超过60-80个字符,最好将其定义为常规嵌套函数。

对于乘法之类的常见操作,请使用operator 模块中的函数,而不要使用lambda函数。例如,推荐使用operator.mul而不是使用lambda x, y: x * y。

END