为了保持后向兼容性时,才会使用 mixedCase 风格命名。比如 threading.py (我在 threading.py 文件中,并没有找到以 mixedCase 风格命名的函数名)。
Python 使用类名改写: 如果类 Foo 存在名为 __a 的属性, Foo.__a 没法访问 __a 。 (一个钻牛角尖的程序员可以使用 Foo._Foo__a 访问 __a)。
双下划线开头的名称能避免和子类发生名称冲突, 通常用在要被继承的类中。
公开 attributes 就是那些你预期让其他用户使用的的 attributes, 你必须保证公开 attributes 的后向兼容性。 非公开 attributes 是那些不准备让第三方用户使用的 attributes,不必保证非公开 attributes 的后向兼容性,它们将来可能会被修改甚至删除。
另一类 API 是 subclass API (其他语言中,常常称为 protected)。有的类是为了要被继承而写的, 不管是要扩展还是要修改类的行为。 当定义一个这样的类时, 仔细决定哪些 attributes 应该是公开的, 哪些应该是 subclass API, 那些应该只在该基类中使用。
公开的 attributes 不要以下划线开头
如果你的公开 attributes 的名字和保留字同名, 那么可以在你的 attribute 名后面添加一个下划线。 这种做法比缩写和 corrupted spelling 要好。(尽管有这条规则, 不论何时如果变量表示一个类,变量都应该使用 cls 命名, 特别的, 类方法第一个参数必须以 cls 命名)。
注意1: 看类方法第一个参数的命名规范。
对于简单的 attributes, 不必添加 accessor/mutator 方法。 当你发现简单 attribute 需要有些功能行为时, Python 提供了方便的方法去扩展。比如,可以使用 property 给访问 attribute 添加隐藏的功能。
注意1: properties 只在新式类中有用。
注意2: 尽量减少隐藏功能的副作用。虽然有的副作用比如缓存总体来说是好的。
注意3: 不要使用 property 实现计算复杂度高的操作, 用户都认为访问 attribute 是个低成本操作。
如果你定义的 attributes 不想让子类用, 那么 attribute 名应该以双下划线开头,且不以下划线结尾。 这样能启动 Python 的名称改写机制。
注意1: 名称改写只用到类名和 attribute 名,所以如果子类和父类同名, attribute 名也相同,那么还是会发生名称冲突。
注意2: 名称改写使得某些应用(比如调试和 __getattr__())变得不方便。 不过名称改写很简单,很容易手动实现。
注意3: 不是所有人都喜欢名称改写。 综合考虑避免名称冲突的需求和方便使用的需求。
公开和内部接口
只有公开接口需要保证后向兼容性。 相应的, 公开接口和内部接口的写法应该明确的区分开。
有文档字符串的接口是公开的接口, 除非文档字符串明确声明它们是内部接口,不保证后向兼容性。所有没有文档字符串的接口都应当是内部接口。
为了更好的支持内省, 模块应该在 __all__ attribute 中明确列出公开 API。 __all__ 取值为空链表就是说模块没有公开 API。
即使正确设置了 __all__, 内部接口仍然应当以单下划线开头。
接口是内部的,如果包含它的接口是内部的。
被引入的名称应当被当成实现细节。 只有当有文档字符串声明它们是模块 API 时(比如 os.path 或者包中的 __init__ 常常把引用的子模块
中的名称声明为公开名称), 它们才能被第三方用户访问。
编程建议
代码应该在所有 Python 实现(PyPy, Jython, IronPython, Cython, Psyco 等等)上都能正常运行。
比如, 不要指望 Cpython 的本地字符串连接实现提供效率,就是用 a += b 或者 a = a + b 表达式。 这种优化就算在 CPython 上都非常脆弱(只对有些类型有效),而且
在不使用引用计数的 Python 实现上没有效果。 实现注重效率的类库时, ‘’.join() 的表述是应该优先使用的。 这能保证, 不管在 Python 的哪种实现上, 连接字符串的时间复杂度都是线性的。
与单例(比如 None) 做比较, 总是应当使用 is 或者 is not , 不要使用 ==。
小心, 如果你要判断一个变量是不是 None, 比如要检验一个默认取值为 None 的变量或者参数被赋值为其他值时, 不要使用 if x, 因为其他值也可能是 falsy (找不到比 falsy 更合适的词)的。
使用 is not 而不要使用 not … is 。 虽然两种表述功能上相同, 但是前一种表述可读性更好。
要实现可以各种比较大小的排序操作时, 最好是实现所有 6 个比较操作(__eq__, __ne__, __lt__, __le__, __gt__, __ge__), 而不是依赖其他代码的默认实现而只实现一种比较操作。
为了减小工作量, 可以使用 functools.total_ordering() 装饰器自动产生没有实现的比较操作。
PEP 207 表明 Python 在比较大小时默认满足自反性。 就是说 Python 解释器可能会把 y > x 语句换成 x < y 语句, y >= x 语句换成 x <= y 语句, 以及 x == y 换成 x != y。 sort() 和 min()
操作保证使用 < 操作, 而 max() 函数保证使用 > 操作。 不过, 为了避免出现问题,最好还是实现所有的比较操作。
不要把 lambda 表达式赋值给变量。定义函数应该使用 def 语句, lambda 表达式只用来写匿名函数。
第一种表述生成的函数对象的 name 属性取值为 f, 而不是像第二种表述那样取值为 ‘lambda’。 这使得函数的 %d 更有意义, traceback 信息更有意义。把 lambda 表达式赋值给变量
让 lambda 表达式失去了它唯一的好处(那就是能嵌入到一个更大的表达式)。
自定义异常要继承自 Exception 而不是 BaseException。捕获直接从 BaseException 继承的异常几乎总是错的。
设计异常的层次时,更应该考虑异常是怎样被捕获的,而不是说异常是如何抛出的。有条理地回答什么出错了, 而不是仅仅声明出错了。(看 PEP 3151 看了实际的例子)
命名异常和命名类采用相同的规范。 如果异常是错误,那么异常名需要以 Error 做后缀。 非错误异常常被当做信号用于非本地工作流控制等场景中。 非错误异常不需要特殊的后缀。
合理使用异常链。 在 Python 3 中, raise X from Y 被用来明确指明用 X 异常替换 Y 异常,同时又不丢失 Y 异常的 traceback。
要故意替换一个 inner 异常时(在 Python 2 中使用 “raise X”, 在 Python 3 中使用 “raise X from None” ),确保相关细节已经传给了新的异常(比如把 KeyError 的属性名传给 ValueError, 或者把原来的异常的消息传给新异常。)
在 Python2 中抛异常, 使用 raise ValueError(‘message’) ,不要用过去的 raise ValueError, ‘message’ 。
后面那种表述,不符合 Python 3 的语法。
前面那种表述同时意味着, 当异常的参数太长或者包括格式化字符串时, 因为括号的存在, 你不需要使用连字符。
捕获异常时,尽量指明要捕获的异常,而不要单单写 except: clause
比如, 使用:
单单使用 except: clause 将会捕获 SystemExitError 和 KeyboardInterrupt 异常, 这使得没法用 Control-C 中断程序, 同时也会导致一些问题。
如果想要捕获所有程序本身的错误,使用 except Exception:`(单单使用 `except 相当于使用 except BaseException:)
只有在下面两种情形下会单单使用 except:
程序作者知道程序有错, 仅仅是为了打印出错误。
代码仅仅做些扫尾工作, 待会还会使用 raise 把异常抛出来。在这种情况下,使用 try … finally 是更好的表述。
要给异常绑定名称, 优先使用 Python 2.6 中引入的显式名称绑定语法。
Python3 只支持这一种给异常绑定名称的语法。 Python2 之前的使用逗号给异常绑定名称的语法会导致语义不明的问题,不要使用。
要捕获操作系统异常时,使用 Python 3.3 引入的显式的异常层次,不要使用 errno 变量。
还有一点, 对于所有的 try/except 语句, try 子句中的代码要竟可能少, 以免太多的代码把 bug 隐藏了起来(不好调试)。
一份资源尽在局部代码段中使用时, 请使用 with 保证资源在使用之后立即且可靠的做了扫尾工作。使用 try/finally 也可以。
上下文管理器应该通过独立的函数或者方法调用,只要它们做了获取和释放资源之外的操作。比如:
后一种表述没有暗示 __enter__ 和 __exit__ 在做完事务时, 除了关闭连接还做了额外的操作。 Being explicit is Important in this case.
return 语句要保持一致。要不所有 return 语句都返回表达式,要不都不返回表达式。 如果一个 return 语句返回表达式,那么对于什么也不返回的 return 语句,应当使用 return None 表述,而且
函数的最后一句应该显示的 return。
使用字符串方法而不是字符串模块
字符串方法总是比 unicode strings 提供的 API 快很多,除非是要与 Python 2.0 之前的Python 后向兼容,否则不要使用 string 模块。(我从来不使用,也不会使用)
使用 ‘’.startswith() 和 ‘’.endswith() 检查前缀后缀,不要使用字符串切片去检查前缀或后缀。
startswith() 和 endswith() 表意更明确更不容易发生错误。比如:
比较对象的类型,请使用 isinstance() , 不要直接比较类型。
判断一个对象是不是字符串时, 记住在 Python2 中, unicode sting 和 str 都是字符串。 unicode string 和 str 有相同的基类: basestring 。所以你可以这样判断:
在 Python3 中, unicode 和 basestring 不再存在。 字节组对象不再是 string(而是一列整数)。
对于序列(字符串,列表,元组), 空序列是 falsy 的:
不要写以空格结尾的字符串字面量。这样的空格让人混淆,甚至有的编辑器会直接删除这样的空格。
布尔值和 True/False 做比较时,不要使用 ==:
Worse:
采纳 PEP 484 以来, 函数注解的规范一直在变。
为保证前向兼容, Python3 代码中的函数注解应当采用 PEP 484 规定的注解语法。
PEP 848 之前推荐的注解的试行规范现在已经不再推荐使用了。
不过,在 Python 标准库之外,推荐尝试 PEP 484 代码规范。 比如, 为第三方库或者应用添上类型注解, 看看添加注解是不是很容易, 有没有提高代码的可读性。
Python 标准库保存地使用注解,新代码已经重大的重构允许使用注解。
对于要另有他用的函数注解,建议在 Python 文件顶部添加如下面所示的注释:
这个注释告诉 type checker 忽略所有注解。(更精细的取消 type checker 报警的方法参见 PEP 484)
和 Linter 一样, type checker 是可选的, 独立的工具。Python 解释器默认不会做类型检查,函数注解默认不能改变代码的行为。
用户可以选择使不使用 type checker 。不过,希望第三方包的使用者可以使用 type checker 对第三方包做检查。为此, PEP 848 推荐
使用 stub 文件: .pyi 文件, type checker 会优先读取 .pyi 文件。stub 文件可以通过类库发布,也可以通过 typeshed repo 发布。
需要保证后向兼容性的代码, 可以把类型注解写到注释中。请查看 PEP 848 中的相关章节。