python 代码风格 -- google code style

代码检查

一定要用pylint检查你的代码.

import

  1. 用 import x 来导入包和模块.
  2. 用 from x import y , 其中x是包前缀, y是不带前缀的模块名.
  3. 在以下情况使用 from x import y as z: 如果有两个模块都叫 y; 如果 y 和当前模块的某个全局名称冲突; 如果 y 是长度过长的名称.
  4. 仅当缩写 z 是标准缩写时才能使用 import y as z.(比如 np 代表 numpy.)

所有新的代码都应该用完整包名来导入每个模块.

异常

使用异常时必须遵守特定要求:

  1. 优先使用合适的内置异常类. 比如, 用 ValueError 表示前置条件错误 (例如给必须为正数的参数传入了负值). 不要使用 assert 语句来验证公开API的参数值. 应该用 assert 来保证内部正确性, 不应该用 assert 来纠正参数或表示意外情况. 若要用异常来表示意外情况, 应该用 raise. 例如:

    正确:

    def connect_to_next_port(self, minimum: int) -> int:
        """连接到下一个可用的端口.
    
        Args:
        minimum: 一个大于等于 1024 的端口号.
    
        Returns:
        新的最小端口.
    
        Raises:
        ConnectionError: 没有可用的端口.
        """
        if minimum < 1024:
            # 注意这里抛出 ValueError 的情况没有在文档里说明,因为 API 的
            # 错误用法应该是未定义行为.
            raise ValueError(f'最小端口号至少为 1024,不能是 {minimum}.')
        port = self._find_next_open_port(minimum)
        if port is None:
            raise ConnectionError(
                f'未能通过 {minimum} 或更高的端口号连接到服务.')
        assert port >= minimum, (
            f'意外的端口号 {port}, 端口号不应小于 {minimum}.')
        return port
    

    错误:

    def connect_to_next_port(self, minimum: int) -> int:
        """连接到下一个可用的端口.
    
        Args:
        minimum: 一个大于等于 1024 的端口号.
    
        Returns:
        新的最小端口.
        """
        assert minimum >= 1024, '最小端口号至少为 1024.'
        port = self._find_next_open_port(minimum)
        assert port is not None
        return port
    
  2. 模块或包可以定义自己的异常类型, 这些类必须继承已有的异常类. 异常类型名应该以 Error 为后缀, 并且不应该有重复 (例如 foo.FooError).

  3. 永远不要使用 except: 语句来捕获所有异常, 也不要捕获 Exception 或者 StandardError , 除非你想:

    1. 重新抛出异常.
    2. 在程序中创造一个隔离点, 记录并抑制异常, 让异常不再继续传播. 这种写法可以用在线程的最外层, 以避免程序崩溃.

    如果你使用这种写法, Python 将非常宽容. except: 真的会捕获任何错误, 包括拼写错误的符号名、 sys.exit() 调用、 Ctrl+C 中断、单元测试错误和各种你不想捕获的错误.

  4. 最小化 try/except 代码块中的代码量. try 的范围越大, 就越容易把你没想到的那些能抛出异常的代码囊括在内. 这样的话, try/except 代码块就掩盖了真正的错误.

  5. 用 finally 表示无论异常与否都应执行的代码. 这种写法常用于清理资源, 例如关闭文件.

全局变量

避免使用全局变量.

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

可以谨慎使用. 尽量避免使用嵌套函数和嵌套类, 除非需要捕获 self 和 cls 以外的局部变量. 不要仅仅为了隐藏一个函数而使用嵌套函数. 应将需要隐藏的函数定义在模块级别, 并给名称加上 _ 前缀, 以便在测试代码中调用此函数.

推导式和生成式

可以用于简单情况. 以下每个部分不应超过一行: 映射表达式、for语句和过滤表达式. 禁止多重for语句和多层过滤. 情况复杂时, 应该用循环.

默认迭代器和操作符

只要是支持的类型 (例如列表、字典和文件), 就使用默认迭代器和操作符. 内置类型也定义了一些返回迭代器的方法. 优先使用返回迭代器的方法, 而非返回列表的方法, 不过注意使用迭代器时不能修改容器.

生成器

可以使用. 生成器的文档字符串中应使用”Yields:”而不是”Returns:”.

(译者注: 参看 注释 )

如果生成器占用了大量资源, 一定要强制清理资源.

一种清理资源的好方法是用上下文管理器包裹生成器 PEP-0533.

Lambda函数

适用于单行函数. 如果函数体超过60-80个字符, 最好还是定义为常规的嵌套函数.

对于乘法等常见操作, 应该用 operator 模块中的函数代替lambda函数. 例如, 推荐用 operator.mul 代替 lambda x, y: x * y .

条件表达式

适用于简单情况. 以下每部分均不得长于一行: 真值分支, if 部分和 else 部分. 情况复杂时应使用完整的if语句.

默认参数值

可以使用, 不过有如下注意事项:

函数和方法的默认值不能是可变对象.

特性 (properties)

允许使用特性. 但是, 和运算符重载一样, 只能在必要时使用, 并且要模仿常规属性的存取特点. 若无法满足要求, 请参考 设置器和写入器 的规则.

举个例子, 一个特性不能仅仅用于获取和设置一个内部属性: 因为不涉及计算, 没有必要用特性 (应该把该属性设为公有). 而用特性来限制属性的访问或者计算 简单 的衍生值则是正确的: 这种逻辑简单明了.

应该用 @property 装饰器 (decorator) 来创建特性. 自行实现的特性装饰器属于威力过大的功能.

特性的继承机制难以理解. 不要用特性实现子类能覆写 (override) 或扩展的计算功能.

True/False的求值

尽可能使用”隐式”假值, 例如: 使用 if foo: 而非 if foo != []: . 不过还是有一些注意事项需要你铭记在心:

  1. 一定要用 if foo is None: (或者 is not None) 来检测 None 值. 例如, 如果你要检查某个默认值为 None 的参数有没有被调用者覆盖, 覆盖的值在布尔语义下可能也是假值!

  2. 永远不要用 == 比较一个布尔值是否等于 False. 应该用 if not x: 代替. 如果你需要区分 False 和 None, 你应该用复合表达式, 例如 if not x and x is not None:.

  3. 多利用空序列(字符串, 列表, 元组)是假值的特性. 因此 if not seq: 比 if len(seq): 更好, if not seq: 比 if not len(seq): 更好.

  4. 处理整数时, 使用隐式false可能会得不偿失(例如不小心将 None 当做0来处理). 你可以显式比较整型值与0的关系 (len() 的返回值例外).

    正确:

    if not users:
        print('无用户')
    
    if i % 10 == 0:
        self.handle_multiple_of_ten()
    
    def f(x=None):
        if x is None:
            x = []
    

    错误:

    if len(users) == 0:
        print '无用户'
    
    if not i % 10:
        self.handle_multiple_of_ten()
    
    def f(x=None):
        x = x or []
    
  5. 注意, ‘0’(字符串, 不是整数)作为布尔值时等于 True.

  6. 注意, Numpy 数组转换为布尔值时可能抛出异常. 因此建议用 .size 属性检查 np.array 是否为空 (例如 if not users.size).

词法作用域(Lexical Scoping, 又名静态作用域)

函数与方法装饰器

仅在有显著优势时, 审慎地使用装饰器. 装饰器的导入和命名规则与函数相同. 装饰器的pydoc注释应清楚地说明该函数是装饰器. 请为装饰器编写单元测试.

避免装饰器自身对外界的依赖(即不要依赖于文件, 套接字, 数据库连接等), 因为执行装饰器时(即导入模块时. pydoc 和其他工具也会导入你的模块) 可能无法连接到这些环境. 只要装饰器的调用参数正确, 装饰器应该 (尽最大努力) 保证运行成功.

装饰器是一种特殊形式的”顶级代码”. 参见关于 Main 的章节.

不得使用 staticmethod, 除非为了兼容老代码库的 API 不得已而为之. 应该把静态方法改写为模块级函数.

仅在以下情况可以使用 classmethod: 实现具名构造函数(named constructor); 在类方法中修改必要的全局状态 (例如进程内共享的缓存等)。

线程

虽然Python的内置类型表面上有原子性, 但是在特定情形下可能打破原子性(例如用Python实现 __hash__ 或 __eq__ 的情况下). 因此它们的原子性不可靠. 你也不能臆测赋值是原子性的(因为赋值的原子性依赖于字典的原子性).

选择线程间的数据传递方式时, 应优先考虑 queue 模块的 Queue 数据类型. 如果不适用, 则使用 threading 模块及其提供的锁原语(locking primitives). 如果可行, 应该用条件变量和 threading.Condition 替代低级的锁.

威力过大的特性

避开这些特性.

可以使用那些在内部利用了这些特性的标准模块和类, 比如 abc.ABCMetadataclasses 和 enum.

现代python: from __future__ imports

from __future__ imports

鼓励使用 from __future__ import 语句. 这样, 你的源代码从今天起就能使用更现代的 Python 语法. 当你不再需要支持老版本时, 请自行删除这些导入语句.

如果你的代码要支持 3.5 版本, 而不是常规的 >=3.7, 请导入:

from __future__ import generator_stop

详情参见 Python future 语句 的文档.

除非你确定代码的运行环境已经足够现代, 否则不要删除 future 语句. 即使你用不到 future 语句, 也要保留它们, 以免其他编辑者不小心对旧的特性产生依赖.

在你认为恰当的时候, 可以使用其他来自 from __future__ 的语句.

代码类型注释

强烈推荐你在更新代码时启用 python 类型分析. 在添加或修改公开API时, 请添加类型注释, 并在构建系统(build system)中启用 pytype. 由于python静态分析是新功能, 因此一些意外的副作用(例如类型推导错误)可能会阻碍你的项目采纳这一功能. 在这种情况下, 建议作者在 BUILD 文件或者代码中添加一个 TODO 注释或者链接, 描述那些阻碍采用类型注释的问题.

参考:

Google 开源项目风格指南——中文版 — Google 开源项目风格指南

Code Review 要点 | 谷歌工程实践 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值