Google Python Style Guide

2020/02/02

Tags: python

来自这里。翻译的目的是为了方便快速理解,所以没有太纠结很准确,最好是和英文对应看。

1 Background

Google 的 python 代码风格指导。

有一个 vim 的配置。 Emacs 默认的似乎就可以。

还有团队使用 yapf 自动格式化代码来避免对格式产生争论。

2 Python Language Rules

2.1 Lint

使用 pylint 检查代码。

可以通过行内注释关闭一些 warning

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

2.2 Imports

例如 sound.effects.echo

1
2
3
from sound.effects import echo
...
echo.EchoFilter(input, output, delay=0.7, atten=4)

2.3 Packages

总是使用全路径导入包。

1
2
3
4
5
# Reference absl.flags in code with the complete name (verbose).
import absl.flags
from doctor.who import jodie

FLAGS = absl.flags.FLAGS
1
2
3
4
5
# Reference flags in code with just the module name (common).
from absl import flags
from doctor.who import jodie

FLAGS = flags.FLAGS

不要像下面这样,假设在 doctor/who/ 下面有个 jodie.py。

1
2
3
4
# Unclear what module the author wanted and what will be imported.  The actual
# import behavior depends on external factors controlling sys.path.
# Which possible jodie module did the author intend to import?
import jodie

这个依赖于 sys.path 的情况,无法知道是想要第三方的 jodie 还是本目录的。

2.4 Exceptions

使用下面这样的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
  def connect_to_next_port(self, minimum):
    """Connects to the next available port.

    Args:
      minimum: A port value greater or equal to 1024.

    Returns:
      The new minimum port.

    Raises:
      ConnectionError: If no available port is found.
    """
    if minimum < 1024:
      # Note that this raising of ValueError is not mentioned in the doc
      # string's "Raises:" section because it is not appropriate to
      # guarantee this specific behavioral reaction to API misuse.
      raise ValueError('Minimum port must be at least 1024, not %d.' % (minimum,))
    port = self._find_next_open_port(minimum)
    if not port:
      raise ConnectionError('Could not connect to service on %d or higher.' % (minimum,))
    assert port >= minimum, 'Unexpected port %d when minimum was %d.' % (port, minimum)
    return port

不要用下面这样的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
  def connect_to_next_port(self, minimum):
    """Connects to the next available port.

    Args:
      minimum: A port value greater or equal to 1024.

    Returns:
      The new minimum port.
    """
    assert minimum >= 1024, 'Minimum port must be at least 1024.'
    port = self._find_next_open_port(minimum)
    assert port is not None
    return port
1
2
3
4
try:
  raise Error()
except Error as error:
  pass

2.5 Global variables

避免使用全局变量。

全局的常量使用全大写。可以在变量前面加 _ 表示是内部的,外部应该使用公共方法来读取这些变量。

2.6 Nested/Local/Inner Classes and Functions

本地局部的函数或者类定义可以方便使用,但是这些类的实例不能被 pickled 序列化。也不能被直接测试,而且还会导致你的程序变长。不要用这个方式来隐藏一些方法,应该使用加 _ 的方法。

2.7 Comprehensions & Generator Expressions

简单的情况可以用。map 表达式,for 语句,filter 表达式。多个 for 或者 filter 不允许。使用循环吧。

 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
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')

不要类似下面这样的,太难理解了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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 Default Iterators and Operators

对于 list,dict,文件,使用自带的迭代器和操作符,这些都针对 in 和 not in 定义了自带的迭代器。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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 Generators

对于迭代器函数,在文档字符串里面使用 "Yields:" 代替 "Returns:"。

2.10 Lambda Functions

单行的时候可以用。如果代码长度超过 60-80 字符,可能使用嵌套函数更好。

使用 operator 模块代替 lambda,例如 operator.mul 代替 lambda x, y: x * y。

2.11 Conditional Expressions

简单的情况可以用。比较复杂的时候,使用完整的 if 语句。

1
2
3
4
5
6
7
one_line = 'yes' if predicate(value) else 'no'
slightly_split = ('yes' if predicate(value)
                  else 'no, nein, nyet')
the_longest_ternary_style_that_can_be_done = (
    'yes, true, affirmative, confirmed, correct'
    if predicate(value)
    else 'no, false, negative, nay')

下面错误的用法

1
2
3
4
5
6
bad_line_breaking = ('yes' if predicate(value) else
                     'no')
portion_too_long = ('yes'
                    if some_long_module.some_long_predicate_function(
                        really_long_variable_name)
                    else 'no, false, negative, nay')

2.12 Default Argument Values

不要使用可变对象(mutable object)作为函数参数的默认值。

1
2
3
4
5
6
7
8
Yes: def foo(a, b=None):
         if b is None:
             b = []
Yes: def foo(a, b: Optional[Sequence] = None):
         if b is None:
             b = []
Yes: def foo(a, b: Sequence = ()):  # Empty tuple OK since tuples are immutable
         ...

可变对象做初始值实际是在方法被 load 的时候就定了,不是调用的时候。

1
2
3
4
5
6
7
8
No:  def foo(a, b=[]):
         ...
No:  def foo(a, b=time.time()):  # The time the module was loaded???
         ...
No:  def foo(a, b=FLAGS.my_thing):  # sys.argv has not yet been parsed...
         ...
No:  def foo(a, b: Mapping = {}):  # Could still get passed to unchecked code
         ...

2.13 Properties

使用 @property 装饰器创建属性字段。

2.14 True/False Evaluations

尽量使用隐含的 false 。例如使用 if foo: 而不用 if foo != []:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Yes: if not users:
         print('no users')

     if foo == 0:
         self.handle_zero()

     if i % 10 == 0:
         self.handle_multiple_of_ten()

     def f(x=None):
         if x is None:
             x = []

No:  if len(users) == 0:
         print('no users')

     if foo is not None and not foo:
         self.handle_zero()

     if not i % 10:
         self.handle_multiple_of_ten()

     def f(x=None):
         x = x or []

2.15 Deprecated Language Features

使用字符串自带的方法,而不用 string 模块。使用函数调用而不是 apply。使用列表生成式和 for 循环代替使用匿名函数的 filte 和 map。使用 for 循环代替 reduce。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Yes: words = foo.split(':')

     [x[1] for x in my_list if x[2] == 5]

     map(math.sqrt, data)    # Ok. No inlined lambda expression.

     fn(*args, **kwargs)

No:  words = string.split(foo, ':')

     map(lambda x: x[1], filter(lambda x: x[2] == 5, my_list))

     apply(fn, args, kwargs)

2.16 Lexical Scoping

python 会创建局部变量,理解清楚的话,一般不会有啥问题。可以用。

2.17 Function and Method Decorators

只在有明确好处的时候使用装饰器。避免使用 @staticmethod ,少用 @classmethod。

装饰器对函数参数和返回结果有绝对权限,所以可以改变一些隐含的行为。另外,装饰器是在 import 的时候执行的,如果有代码错误可能程序就崩了。

装饰器的文档应该明确说明这个是装饰器,应该给装饰器写测试用例。

在装饰器里面避免外部依赖(例如文件,sockets,数据库连接什么的),因为那些在装饰器运行的时候可能不存在(在import 阶段,例如在 pydoc 或者其他工具里面)。装饰器应该要保证在各种情况下都可以成功。

不要使用 @staticmethod ,除非为了和已有库的 api 定义集成。应该使用模块级别的函数代替。

只在定义命名构造方法或者类级别的方法都时候使用 @classmethod,例如修改全局状态或者缓存。

2.18 Threading

不要依赖内部自带类型的原子性。

python 的一些自带类型例如 dict 似乎支持原子操作,但是有些情况下又不原子。也不要依赖变量赋值的原子性。

使用 Queue 模块的 Queue 类型来作为线程间数据通讯的方法。或者使用 threading 模块和他提供的 locking 方法。学习下如何使用 condition variables ,使用 threading.Condition 代替使用 lower-level locks.

2.19 Power Features

尽量避免使用。

例如自定义 metaclass,接触 bytecode,on-the-fly 编译,动态继承,等等吧。。。

标准库里面的模块使用到了没关系,例如 abc.ABCMeta, collections.namedtuple, dataclasses, and enum

2.20 Modern Python: Python 3 and from future imports

应该写兼容 python3 的代码。

2.21 Type Annotated Code

python3 支持 type hint,可以使用 pytype 检查。

强烈建议更新代码的时候使用 type 支持。

3 Python Style Rules

3.1 Semicolons

不要用分号结尾。不要用分号把两行放一行。

3.2 Line length

一行 80 个字符。除非:

不要使用 \ 切分多行,除非是 with 语句里面有多个 context 管理器。

有必要的话可以增加多余的括号。

1
2
3
4
5
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'):

单行字符串太长的话,使用括号切分成多行。

1
2
x = ('This will build a very long long '
     'long long long long long long string')

注释里面的 url 尽量单独放一行

1
2
3
4
5
6
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。注意缩进。

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

No:  with VeryLongFirstExpressionFunction() as spam, \
          VeryLongSecondExpressionFunction() as beans:
       PlaceOrder(eggs, beans, spam, beans)

Yes:  with very_long_first_expression_function() as spam:
          with very_long_second_expression_function() as beans:
              place_order(beans, spam)

如果还有其他超过 80 字符的情况,并且 yapf 工具也不能处理的话,可以容忍。

3.3 Parentheses

尽量少用括号。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
Yes: if foo:
         bar()
     while x:
         x = bar()
     if x and y:
         bar()
     if not x:
         bar()
     # 只有一个元素的元组使用 () 比只有一个逗号清晰
     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 Indentation

使用 4 个空格缩进。

 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
36
37
38
39
40
41
42
43
44
45
Yes:   # 和开始的括号对齐
       foo = long_function_name(var_one, var_two,
                                var_three, var_four)
       meal = (spam,
               beans)

       # 和字典的开始括号对齐
       foo = {
           long_dictionary_key: value1 +
                                value2,
           ...
       }

       # 4个空格的悬挂缩进;第一行什么都不放
       foo = long_function_name(
           var_one, var_two, var_three,
           var_four)
       meal = (
           spam,
           beans)

       # 字典里面的 4 空格悬挂缩进
       foo = {
           long_dictionary_key:
               long_dictionary_value,
           ...
       }

No:    # 看不清第一行的内容了
       foo = long_function_name(var_one, var_two,
           var_three, var_four)
       meal = (spam,
           beans)

       # 2 空格缩进
       foo = long_function_name(
         var_one, var_two, var_three,
         var_four)

       # 字典里面没有悬挂缩进
       foo = {
           long_dictionary_key:
           long_dictionary_value,
           ...
       }
3.4.1 Trailing commas in sequences of items?

结尾的逗号只有在 ],},) 和最后的元素不在同一行的时候使用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Yes:   golomb3 = [0, 1, 3]
Yes:   golomb4 = [
           0,
           1,
           4,
           6,
       ]

No:    golomb4 = [
           0,
           1,
           4,
           6
       ]

3.5 Blank Lines

顶级定义间两个空行。方法之间,class 和第一个方法之前使用一个空行。def 行之后不要有空行。适当的在方法和函数里面使用一个空行。

3.6 Whitespace

在括号里面,括号和内容间不要有空格。

1
2
3
Yes: spam(ham[1], {eggs: 2}, [])

No:  spam( ham[ 1 ], { eggs: 2 }, [ ] )

逗号,分号,冒号前不要加空格,除了行尾,后面需要加空格。

1
2
3
4
5
6
7
Yes: if x == 4:
         print(x, y)
     x, y = y, x

No:  if x == 4 :
         print(x , y)
     x , y = y , x

参数列表,索引,切片的左括号前面不加空格

1
2
3
4
5
6
7
Yes: spam(1)

No:  spam (1)

Yes: dict['key'] = list[index]

No:  dict ['key'] = list [index]

行尾不加多余的空格。操作符两边加空格。

1
2
3
Yes: x == 1

No:  x<1

传参数或者函数默认值的 = 左右不加空格。

1
2
3
4
5
Yes: def complex(real, imag=0.0): return Magic(r=real, i=imag)
Yes: 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)
No:  def complex(real, imag: float=0.0): return Magic(r = real, i = imag)

不要使用空格做竖列对齐,这个维护起来容易成负担。有一些工具或者 ide 可以自动做这个事情,但是确实对不使用这些工具的人是个负担。

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

大部分 .py 文件都不需要 #! 这行。主文件可以使用 #!/usr/bin/python 加 2 或者 3 结尾。

这个只有直接运行的主程序有用,对于 import 的模块没用。

3.8 Comments and Docstrings

确保针对模块,函数,方法使用了正确的文档字符串和行内的注释。

3.8.1 Docstrings

总是使用 """ 格式的文档字符串。

3.8.2 Modules

每个文件都有 lincense 声明,文件开头说明下模块内容和示例。

1
2
3
4
5
6
7
8
9
"""用一行简介模块或者程序功能

留一个空行。后面说明模块的说明,可以加例子。

  Typical usage example:

  foo = ClassFoo()
  bar = foo.FunctionBar()
"""
3.8.3 Functions and Methods

主要需要说明参数,返回值,异常。

 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
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 Classes

如果有公共属性,需要说明下。

 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."""
3.8.5 Block and Inline Comments

对于代码里面逻辑复杂或者难理解的地方,如果需要在 code review 的时候解释说明,那最好直接写到代码里面。

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.

inline 的注释应该在 # 前留两个空格。另外,注释里面不要去直接解释代码,这个没啥意义。

3.8.6 Punctuation, Spelling, and Grammar

应该是说注意语法啥的,代码的质量也包括注释的质量。

3.9 Classes

如果一个类没有明显的基类,那就继承 object。

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

更好的兼容性。还帮忙定义了一些 __ 开头的方法。

3.10 Strings

使用 format 或者 % 格式化字符串。python 3.6 还支持了 f-string。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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: x = '%s%s' % (a, b)  # use + in this case
    x = '{}{}'.format(a, b)  # use + in this case
    x = first + ', ' + second
    x = 'name: ' + name + '; score: ' + str(n)

避免在循环里面使用 + 和 += 操作符连接字符串。因为 string 是 immutable ,这样会创建很多临时对象。可以使用个 list 然后 ''.join(list) 这样,或者使用 io.BytesIO。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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>'

灵活使用 ' 和 " ,避免使用 \\ 转义。多行字符串使用 """ ,不用 ''' 。

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

  Yes:
  long_string = ("And this is fine if you can not accept\n" +
                 "extraneous leading spaces.")

  Yes:
  long_string = ("And this too is fine if you can not accept\n"
                 "extraneous leading spaces.")

  Yes:
  import textwrap

  long_string = textwrap.dedent("""\
      This is also fine, because textwrap.dedent()
      will collapse common leading spaces in each line.""")

3.11 Files and Sockets

使用完 file 和 sockets 之后显式的关闭。要不然

当文件或者sockets 对象被销毁的时候会自动关闭,但是依赖这个特性是个不好的习惯

使用 with 语句来打开文件

1
2
3
with open("hello.txt") as hello_file:
    for line in hello_file:
        print(line)

文件类型的对象,但是还不支持 with 语句的,可以使用 contextlib.closing()

1
2
3
4
5
import contextlib

with contextlib.closing(urllib.urlopen("http://www.python.org/")) as front_page:
    for line in front_page:
        print(line)

3.12 TODO Comments

注释里面使用 TODO 来标记临时的一些处理,或者一些有待改进的处理。

TODO 使用 TODO 开头,后面括号里面使用邮件或者名字标记是谁加的,然后跟着是 todo 内容。

这个格式要求是为了将来方便搜索。TODO 并不表示以后也需要这个人去修复这个问题。

1
2
# TODO([email protected]): Use a "*" here for string repetition.
# TODO(Zeke) Change this to use relations.

如果一个 TODO 是为了标记将来做什么,那最好把时间 (“Fix by November 2009”) 或者触发的事件 (“Remove this code when all clients can handle XML responses.”) 也加上。

3.13 Imports formatting

一个模块一行

1
2
3
4
Yes: import os
     import sys

No:  import os, sys

import 都写在文件顶部,在模块的文档字符串之后,在模块的全局变量和常量之前。按照最通用到最不通用排序。

  1. future import 语句来打开文件

1
2
3
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
  1. python 标准库

1
import sys
  1. 第三方库

1
import tensorflow as tf
  1. 代码库子模块

1
from otherproject.ai import mind
  1. 程序自己的子模块(已经废弃)。新的处理方式是和 4 一样处理就行。

使用词典序,忽略大小写,基于模块包的全称。在不同块之前增加一个空行。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import collections
import queue
import sys

from absl import app
from absl import flags
import bs4
import cryptography
import tensorflow as tf

from book.genres import scifi
from myproject.backend.hgwells import time_machine
from myproject.backend.state_machine import main_loop
from otherproject.ai import body
from otherproject.ai import mind
from otherproject.ai import soul

# Older style code may have these imports down here instead:
#from myproject.backend.hgwells import time_machine
#from myproject.backend.state_machine import main_loop

3.14 Statements

通常一行一句。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Yes:

  if foo: bar(foo)

No:

  if foo: bar(foo)
  else:   baz(foo)

  try:               bar(foo)
  except ValueError: baz(foo)

  try:
      bar(foo)
  except ValueError: baz(foo)

3.15 Accessors

如果比较简单,可以使用公共属性,而不用 geter/seter,毕竟没必要多一次函数调用。稍微复杂点的可以使用 @property。

对于复杂的,可以使用 get_foo() 和 set_foo() 这样的函数调用。如果以前允许通过属性访问,不要把新的函数直接绑定过去。这样依然用原来的方法的那些代码就会报错,可以让他们意识到新的逻辑的复杂程度。

3.16 Naming

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 扩展名。

3.16.1 Names to Avoid
3.16.2 Naming Conventions
3.16.3 File Naming

必须是 .py 结尾,不能包含 - 。如果你想要一个文件没有扩展名,那可以建一个软连接或者写个 bash 文件包含 exec "$0.py" "[email protected]"。

3.16.4 Guidelines derived from Guido’s Recommendations
TypePublicInternal
Packageslower_with_under
Moduleslower_with_under_lower_with_under
ClassesCapWords_CapWords
ExceptionsCapWords
Functionslower_with_under()_lower_with_under()
Global/Class ConstantsCAPS_WITH_UNDER_CAPS_WITH_UNDER
Global/Class Variableslower_with_under_lower_with_under
Instance Variableslower_with_under_lower_with_under (protected)
Method Nameslower_with_under()_lower_with_under() (protected)
Function/Method Parameterslower_with_under
Local Variableslower_with_under

不鼓励使用 __ 开头的变量。

3.17 Main

即使一个文件是个可执行的文件,也应该允许被 import,并且 import 不能有副作用,例如直接运行。主要的功能应该在 main() 函数里面。

pydoc 和单元测试都需要文件是可以被 import 的。程序应该总是检查 __name__

1
2
3
4
5
def main():
    ...

if __name__ == '__main__':
    main()

注意要兼容 pydoc ,顶级的函数调用,创建对象以及其他操作都会在 import 的时候就执行。

3.18 Function length

最好是短小精悍的。这里没有硬性规定,超过 40 行的,看看在不影响程序结构的情况下是不是可以拆分。

即使长的函数现在看着没啥问题,但是过几个月之后,需要修改或者增加新功能的时候会比较痛苦。

3.19 Type Annotations

3.19.1 General Rules
3.19.2 Line Breaking

参考前面的缩进的定义。

增加了类型注解之后,很多函数的签名会变成一个参数一行。

1
2
3
4
5
def my_method(self,
              first_var: int,
              second_var: Foo,
              third_var: Optional[Bar]) -> int:
  ...

Always prefer breaking between variables, and not for example between variable names and type annotations. However, if everything fits on the same line, go for it.

1
2
def my_method(self, first_var: int) -> int:
  ...

如果返回函数名参数加返回类型太长,可以另起一行空 4 个空格。

1
2
3
def my_method(
    self, first_var: int) -> Tuple[MyLongType1, MyLongType1]:
  ...

更长的情况

1
2
3
4
5
Yes:
def my_method(
    self, other_arg: Optional[MyLongType]
) -> Dict[OtherLongType, MyLongType]:
  ...

pylint 允许和括号对齐,但是这样会降低可读性。

1
2
3
4
5
No:
def my_method(self,
              other_arg: Optional[MyLongType]
             ) -> Dict[OtherLongType, MyLongType]:
  ...

类似上面的例子,尽量不打断类型注解。但是如果太长,也可以换行,保持子类型不被打断。

1
2
3
4
5
6
7
def my_method(
    self,
    first_var: Tuple[List[MyLongType1],
                     List[MyLongType2]],
    second_var: List[Dict[
        MyLongType3, MyLongType4]]) -> None:
  ...

If a single name and type is too long, consider using an alias for the type. The last resort is to break after the colon and indent by 4.

如果一个类型的名字自己本身太长了,考虑定义一个别名。实在没办法,在冒号后面换行。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Yes:
def my_function(
    long_variable_name:
        long_module_name.LongTypeName,
) -> None:
  ...

No:
def my_function(
    long_variable_name: long_module_name.
        LongTypeName,
) -> None:
  ...
3.19.3 Forward Declarations

如果需要一个在模块里面还没有定义的类名,可以使用字符串名字。

1
2
3
4
class MyClass(object):

  def __init__(self,
               stack: List["MyClass"]) -> None:
3.19.4 Default Values

基于 PEP-008,只有在同时有类型注解和默认值的时候 = 前后才会同时有空格。

1
2
3
4
5
6
7
Yes:
def func(a: int = 0) -> int:
  ...

No:
def func(a:int=0) -> int:
  ...
3.19.5 NoneType

NoneType 是个 fist class 类型,None 是 NoneType 的别名。如果一个参数可以是 None,那应该声明下。你可以使用 Union,但是如果只有一种其他类型,可以使用 Optional。

使用明确的 Optional 定义,早先的 PEP-484 允许 a: Text = None 解释为 a: Optional[Text] = None ,但是现在不推荐了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Yes:
def func(a: Optional[Text], b: Optional[Text] = None) -> Text:
  ...
def multiple_nullable_union(a: Union[None, Text, int]) -> Text
  ...

No:
def nullable_union(a: Union[None, Text]) -> Text:
  ...
def implicit_optional(a: Text = None) -> Text:
  ...
3.19.6 Type Aliases

可以给类型建别名,命名应该是大写开头的。如果只在这个模块里面使用,应该使用 _ 开头。

1
2
_ShortName = module_with_long_name.TypeWithLongName
ComplexMap = Mapping[Text, List[Tuple[int, int]]]

类似的还有嵌套的类型定义,或者函数返回的多变量。

3.19.7 Ignoring Types

可以使用 # type: ignore 注释关闭单行的类型检查。

pytype 支持关闭特定的类型错误。

1
# pytype: disable=attribute-error
3.19.8 Typing Variables

如果一个变量很难通过推导得出,那可以在注释或者冒号后面定义类型。

1
2
a = SomeUndecoratedFunction()  # type: Foo
a: Foo = SomeUndecoratedFunction()
3.19.9 Tuples vs Lists

不像 list 只能有一个类型,tuple 可以有多个。

1
2
3
a = [1, 2, 3]  # type: List[int]
b = (1, 2, 3)  # type: Tuple[int, ...]
c = (1, "2", 3.5)  # type: Tuple[int, Text, float]
3.19.10 TypeVars

python 的 type 支持范型。通过 TypeVar 这个工厂方法使用。

1
2
3
4
5
from typing import List, TypeVar
T = TypeVar("T")
...
def next(l: List[T]) -> T:
  return l.pop()

可以给 TypeVar 增加限制条件

1
2
3
AddableType = TypeVar("AddableType", int, float, Text)
def add(a: AddableType, b: AddableType) -> AddableType:
  return a + b

有个比较常用的类型是 AnyStr,用它来表示 bytes 或者 unicode 。

1
2
3
4
5
from typing import AnyStr
def check_length(x: AnyStr) -> AnyStr:
  if len(x) <= 42:
    return x
  raise ValueError()
3.19.11 String types

对于 python3 使用 str。其他说明都是些关于 2 和 3 兼容的。

3.19.12 Imports For Typing

只导入需要的类型,可以一行导入多个。这些类型也应该作为关键字,不要在你的代码里面使用,如果有冲突可以通过 as 来起个别名。

1
2
from typing import Any, Dict, Optional
from typing import Any as AnyType
3.19.13 Conditional Imports

只有在实验情况下才使用条件导入。

3.19.14 Circular Dependencies

类型的循环引用一般都是有代码问题,这些代码应该被重构。

使用 Any 替换那些导致循环引用的模块。后面使用这个模块真实的类型定义(不过 Any 的属性还是 Any)。

1
2
3
4
5
6
7
from typing import Any

some_mod = Any  # some_mod.py imports this module.
...

def my_method(self, var: some_mod.SomeType) -> None:
  ...
3.19.15 Generics

When annotating, prefer to specify type parameters for generic types; otherwise, the generics’ parameters will be assumed to be Any.

1
2
3
4
5
6
7
8
9
def get_names(employee_ids: List[int]) -> Dict[int, Any]:
  ...

# These are both interpreted as get_names(employee_ids: List[Any]) -> Dict[Any, Any]
def get_names(employee_ids: list) -> Dict:
  ...

def get_names(employee_ids: List) -> Dict:
  ...

If the best type parameter for a generic is Any, make it explicit, but remember that in many cases TypeVar might be more appropriate:

1
2
3
4
5
6
def get_names(employee_ids: List[Any]) -> Dict[Any, Text]:
  """Returns a mapping from employee ID to employee name for given IDs."""

T = TypeVar('T')
def get_names(employee_ids: List[T]) -> Dict[T, Text]:
  """Returns a mapping from employee ID to employee name for given IDs."""

4 Parting Words

保持一致性。

编辑代码的时候,注意看看原来的代码的风格。别破坏原来的代码风格。

代码风格指导的目的是为了让大家形成一种共同语言,这样更多的关注代码的逻辑。如果一个代码里面语言太多反而会导致阅读代码变困难,所以应该避免。

Comments