介绍(Introduction)
本文档给出了Python代码的编码约定,该Python代码包含主Python发行版中的标准库。请参阅随附的信息性PEP,该PEP描述了Python2的C实现中C代码的样式准则。
本文档和PEP 257(Docstring约定)是从Guido最初的Python样式指南文章中改编而来,并对Barry的样式指南3进行了一些补充。
该样式指南会随着时间的流逝而发展,因为会确定其他约定,而过去的约定会因语言本身的更改而过时。
许多项目都有自己的编码风格准则。如有任何冲突,此类项目特定的指南优先于该项目。
尽信书不如无书(A Foolish Consistency is the Hobgoblin of Little Minds)
Guido的主要见解之一是代码的读取比编写的次数要多
。此处提供的指南旨在提高代码的可读性,并使其在各种Python代码中保持一致。正如PEP 20所说,“可读性至关重要”。
样式指南是关于一致性的。与该样式指南的一致性很重要。项目内的一致性更为重要。一个模块或功能内的一致性是最重要的。
但是,要知道什么时候不一致-有时样式指南的建议就不适用。如有疑问,请运用最佳判断。查看其他示例并确定最合适的方法。不要犹豫,问!
特别是:不要仅仅为了遵守本PEP而向后兼容!
忽略特定准则的其他一些好的理由:
- 应用指南时,即使对于那些习惯于阅读遵循此PEP的代码的人,也会使代码的可读性降低。
- 为了与周围的代码一致(也可能是出于历史原因),该代码也会破坏它(尽管这也是清理别人的混乱的机会(采用真正的XP风格))。
- 由于所讨论的代码早于准则的引入,因此没有其他理由修改该代码。
- 当代码需要与不支持样式指南建议功能的旧版Python保持兼容时。
代码布局(Code Lay-out)
缩进(Indentation)
每个缩进层使用4个空格。
- 延续行应该垂直对齐封装的元素,或者使用Python的隐式行连接括号、方括号和大括号,或者使用
悬挂式缩进
。使用悬挂式缩进时,应考虑以下几点:第一行不应该有任何参数,应该使用进一步的缩进来清楚地将其本身作为延续行区分开来。
正例:
# 与左括号对齐,PyCharm默认
foo = long_function_name(var_one, var_two,
var_three, var_four)
# 添加4个空格(额外的缩进级别)以将参数与其余参数区分开。PyCharm默认
def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)
# 悬挂的缩进应添加一个级别。PyCharm默认
foo = long_function_name(
var_one, var_two,
var_three, var_four)
反例:
# 当不使用垂直对齐时,禁止第一行上的参数。
foo = long_function_name(var_one, var_two,
var_three, var_four)
# 由于缩进无法区分,需要进一步缩进。
def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)
对于连续行,4空格规则是可选的。
可选的:
# 悬挂缩进*可以*除4个空格外缩进。
foo = long_function_name(
var_one, var_two,
var_three, var_four)
当if语句的条件部分长到需要换行写的时候,注意可以在两个字符关键字的连接处(比如if),增加一个空格,再增加一个左括号来创造一个4空格缩进的多行条件。这会与if语句内同样使用4空格缩进的代码产生视觉冲突。PEP没有明确指明要如何区分if的条件代码和内嵌代码。可使用的选项包括但不限于下面几种情况:
正例:
# 没有额外的缩进
if (this_is_one_thing and
that_is_another_thing):
do_something()
# 添加一个注释,它将提供编辑器中支持语法高亮显示的一些区别。
if (this_is_one_thing and
that_is_another_thing):
# Since both conditions are true, we can frobnicate.
do_something()
# 在条件延续行上添加一些额外的缩进。PyCharm默认
if (this_is_one_thing
and that_is_another_thing):
do_something()
另外,请参阅下面关于在二进制操作符之前或之后中断的讨论。
多行结构的 右
大括号、方括号、圆括号可以排列在列表最后一行的第一个非空格字符下,如:
正例:
my_list = [
1, 2, 3,
4, 5, 6,
]
result = some_function_that_takes_arguments(
'a', 'b', 'c',
'd', 'e', 'f',
)
也可以将其排列在开始多行构造的行的第一个字符之下,如:
正例:
# PyCharm默认
my_list = [
1, 2, 3,
4, 5, 6,
]
result = some_function_that_takes_arguments(
'a', 'b', 'c',
'd', 'e', 'f',
)
制表符还是空格(Tabs or Spaces?)
空格是首选的缩进方法。
- 制表符应该只用于与已经用制表符缩进的代码保持一致。
- Python 3不允许混合使用制表符和空格进行缩进。
- 用制表符和空格混合缩进的Python 2代码应该转换为只使用空格。
- 在使用
-t
选项调用Python 2命令行解释器时,它会发出关于非法混合制表符和空格的代码的警告。当使用-tt
时,这些警告将变成错误。强烈推荐这些选项!
行的最大长度(Maximum Line Length)
将所有行限制为最多79个字符。
- 对于具有较少结构限制(文档字符串或注释)的长文本块,行长度应该限制为72个字符。
- 限制所需的编辑器窗口宽度可以同时打开多个文件,并且在使用在相邻列中显示两个版本的代码审查工具时效果很好。
- 大多数工具的默认包装破坏了代码的可视化结构,使代码更难理解。选择这些限制是为了避免在窗口宽度设置为80的编辑器中进行包装,即使该工具在包装行时在最后一列中放置标记符号。一些基于web的工具可能根本不提供动态换行。
- 有些队非常喜欢较长的队形。对于由能够在这个问题上达成一致的团队专门或主要维护的代码,可以将行长度限制增加到99个字符,前提是注释和文档字符串仍然以72个字符包装。
- Python标准库比较保守,要求行数限制为79个字符(文档字符串/注释限制为72个字符)。
- 包装长行代码的首选方法是在圆括号、方括号和大括号中使用Python的隐含行延续。通过将表达式括在括号中,可以将长行分解成多行。这些应该优先用于行延续,而不是使用反斜杠。
- 有时反斜杠可能仍然合适。 例如,长的多个with语句不能使用隐式连续,因此反斜杠是可以接受的:
正例:
with open('/path/to/some/file/you/want/to/read') as file_1, \
open('/path/to/some/file/being/written', 'w') as file_2:
file_2.write(file_1.read())
请参阅前面有关多行if语句的讨论,以获取有关缩进此类多行带状态语句的进一步思考。
另一种此类情况是使用assert语句。
确保续行进行适当缩进。
换行符应该在二进制运算符之前还是之后(Should a Line Break Before or After a Binary Operator?)
- 几十年来,推荐的样式是在二元运算符之后使用。 但这会以两种方式损害可读性:运算符趋向于分散在屏幕上的不同列上,并且每个运算符都从其操作数移至上一行。 在这里,眼睛必须做额外的工作才能分辨出添加了哪些项目和减去了哪些项目:
反例:
# 操作符远离它们的操作数
income = (gross_wages +
taxable_interest +
(dividends - qualified_dividends) -
ira_deduction -
student_loan_interest)
- 为了解决这个可读性问题,数学家和他们的出版商遵循相反的惯例。Donald Knuth解释了他的计算机和排版系列中的传统规则:“尽管一个段落中的公式总是在二元运算和关系之后断开,但是显示的公式总是在
二元运算符之前断开
。” 4
遵循数学的传统能产出更多可读性高的代码:
正例:
# 容易匹配操作符和操作数
income = (gross_wages
+ taxable_interest
+ (dividends - qualified_dividends)
- ira_deduction
- student_loan_interest)
在Python代码中,允许在二元运算符之前或之后中断,只要本地的约定是一致的。对于新代码,建议使用Knuth的样式。
空行(Blank Lines)
- 用
两个空行
包围顶级函数和类定义。 - 类内部的方法定义由
单个空白行
包围。 - 多余的空白行可以(分别)用于分隔相关功能组。 一堆相关的单线之间(例如,一组虚拟实现)可以省略空白行。
- 在函数中使用空白行,以节省空间,以指示逻辑部分。
- Python接受control-L(即^ L)换页符作为空格;许多工具将这些字符视为页面分隔符,因此您可以使用它们来分隔文件相关部分的页面。 请注意,某些编辑器和基于Web的代码查看器可能无法将Control-L识别为换页,而将在其位置显示另一个标志符号。
源文件编码(Source File Encoding)
- 核心Python发行版中的代码应始终使用UTF-8(或Python 2中的ASCII)。
- 使用ASCII(在Python 2中)或UTF-8(在Python 3中)的文件不应具有编码声明。
- 在标准库中,非默认编码仅应用于测试目的,或者在注释或文档字符串需要提及包含非ASCII字符的作者姓名时; 否则,使用\ x,\ u,\ U或\ N转义是在字符串文字中包含非ASCII数据的首选方法。
- 对于Python 3.0及更高版本,标准库规定了以下策略(请参阅PEP 3131):Python标准库中的所有标识符务必使用纯ASCII标识符,并且在可行的情况下应使用英文单词(在许多情况下,缩写和技术 使用非英语的术语)。 此外,字符串文字和注释也必须使用ASCII。 唯一的例外是(a)测试非ASCII功能的测试用例,以及(b)作者的姓名。 名称不基于拉丁字母(latin-1,ISO / IEC 8859-1字符集)的作者必须在此字符集中提供其姓名的音译。
- 鼓励具有全球受众的开源项目采用类似的政策。
导入(Imports)
- 导入包操作,通常在分开的行:
正例:
import os
import sys
反例:
import sys, os
但是可以这样:
正例:
from subprocess import Popen, PIPE
- 导入包操作,总是位于
文件的顶部
,在模块的注释和文档字符串之后
,在模块的全局变量和常量之前
。
导入包操作,应按以下顺序分组:
- 标准库导入。
- 相关第三方进口。
- 本地应用程序/库特定的导入。
- 应在每组导入之间放置一个空白行。
-
推荐使用绝对路径导入
,如果导入系统配置不正确(比如程序包中的一个目录在sys.path里的路径后),使用绝对路径导入会更具可读性,并且性能更好(至少会提供更好的错误信息):
正例:
import mypkg.sibling
from mypkg import sibling
from mypkg.sibling import example
- 然而,显示的指定相对导入路径是使用绝对路径的一种可接受的替代方案,特别是在处理复杂的包装布局时,使用绝对路径导入不必要的冗长的复杂包布局时:
正例:
from . import sibling
from .sibling import example
- 标准库代码应避免使用复杂的程序包结构,并始终使用绝对路径导入。
- 绝对不要使用隐式相对导入,并且在Python 3中已将其删除。
- 当从一个包含类的模块中导入类时,通常可以这样拼写:
正例:
from myclass import MyClass
from foo.bar.yourclass import YourClass
- 如果上述的写法导致名字的冲突,那么这么写:
正例:
import myclass
import foo.bar.yourclass
使用时:
myclass.MyClass
foo.bar.yourclass.YourClass
- 应避免使用通配符的导入(
from <module> import *
),因为通配符的使用会导致不清楚命名空间中存在哪些名字,这会使得读取接口和许多自动化工具之间产生混淆。 对于通配符的导入,有一个合理的用例,即将内部接口重新发布为公共API的一部分(例如,用可选的加速器模块中的定义覆盖纯Python实现的接口,以及重写那些事先不知道的定义)。 - 以这种方式重新发布名称时,以下有关公共和内部接口的准则仍然适用。
模块级的“呆”名(Module Level Dunder Names)
- 诸如
__all __
,__ author __
,__ version__
等的模块级别“呆名”(即带有两个前导下划线和两个下划线的名称)应放置在模块文档字符串之后,但应放在除from __future__
导入之外的任何import语句之前。 Python要求将来导入必须在模块中出现在除文档字符串以外的任何其他代码之前:
正例:
"""This is the example module.
This module does stuff.
"""
from __future__ import barry_as_FLUFL
__all__ = ['a', 'b', 'c']
__version__ = '0.1'
__author__ = 'Cardinal Biggles'
import os
import sys
字符串引号(String Quotes)
- 在Python中,
单引号字符串和双引号字符串是相同的
。 本PEP对此不做任何建议。 选择一条规则并坚持下去。 但是,当字符串包含单引号或双引号字符时,请使用另一个以避免在字符串中使用反斜杠。 它提高了可读性。 - 对于三引号字符串,请始终使用双引号字符以与PEP 257中的docstring约定一致。
表达式和语句中的空格(Whitespace in Expressions and Statements)
忍无可忍(Pet Peeves)
在以下情况下避免不必要的空格:
- 紧接在圆括号、方括号或大括号内:
正例: spam(ham[1], {eggs: 2})
反例: spam( ham[ 1 ], { eggs: 2 } )
- 在尾随逗号和其后的右括号之间:
正例: foo = (0,)
反例: bar = (0, )
- 紧接在逗号、分号或冒号之前:
正例: if x == 4: print x, y; x, y = y, x
反例: if x == 4 : print x , y ; x , y = y , x
- 但是,在切片中,冒号的作用类似于二元运算符,并且在每一侧都应具有相等的数量(将其视为优先级最低的运算符)。 在扩展切片中,两个冒号必须应用相同的间距。 例外:省略slice参数时,将省略空格。
正例:
ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:]
ham[lower:upper], ham[lower:upper:], ham[lower::step]
ham[lower+offset : upper+offset]
ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)]
ham[lower + offset : upper + offset]
反例:
ham[lower + offset:upper + offset]
ham[1: 9], ham[1 :9], ham[1:9 :3]
ham[lower : : upper]
ham[ : upper]
- 紧接在开始函数调用的参数列表的左括号之前:
正例: spam(1)
反例: spam (1)
- 紧接在开始索引或切片的左括号之前:
正例: dct['key'] = lst[index]
反例: dct ['key'] = lst [index]
- 赋值(或其他)运算符周围的多个空格,以使其与其他运算符对齐。
正例:
x = 1
y = 2
long_variable = 3
反例:
x = 1
y = 2
long_variable = 3
其他建议(Other Recommendations)
- 避免在任何地方拖尾空格。 由于它通常是不可见的,因此可能会造成混淆: 反斜杠后跟一个空格和一个换行符不算作行继续标记。 一些编辑器没有保留它,并且许多项目(例如CPython本身)都具有拒绝它的预提交钩子。
- 始终在两侧用单个空格将这些二进制运算符引起来:赋值(=),扩充赋值(+ =,-=等),比较(==,<,>,!=,<>,<=,> = ,in,not in,is,is not),布尔值(and,or,not)。
- 如果使用优先级不同的运算符,请考虑在优先级最低的运算符周围添加空格。 使用您自己的判断; 但是,永远不要使用一个以上的空间,并且在二进制运算符的两边总是具有相同数量的空白。
正例:
i = i + 1
submitted += 1
x = x*2 - 1
hypot2 = x*x + y*y
c = (a+b) * (a-b)
反例:
i=I+1
submitted +=1
x = x * 2 - 1
hypot2 = x * x + y * y
c = (a + b) * (a - b)
- 函数注释应该使用冒号的一般规则,如果出现->箭头,则始终在->箭头周围留有空格。(有关函数注释的更多信息,请参见下面的函数注释。)
正例:
def munge(input: AnyStr): ...
def munge() -> PosInt: ...
反例:
def munge(input:AnyStr): ...
def munge()->PosInt: ...
- 当用于指示关键字参数时,或用于指示未加注释的函数参数的默认值时,不要在=符号周围使用空格。
正例:
def complex(real, imag=0.0):
return magic(r=real, I=imag)
反例:
def complex(real, imag = 0.0):
return magic(r = real, i = imag)
- 但是,在将参数注释与默认值组合使用时,请在=符号周围使用空格:
正例:
def munge(sep: AnyStr = None): ...
def munge(input: AnyStr, sep: AnyStr = None, limit=1000): ...
反例:
def munge(input: AnyStr=None): ...
def munge(input: AnyStr, limit = 1000): ...
- 复合语句(同一行的多个语句)通常不建议使用。
正例:
if foo == 'blah':
do_blah_thing()
do_one()
do_two()
do_three()
反例:
if foo == 'blah': do_blah_thing()
do_one(); do_two(); do_three()
- 虽然有时候在同一行中使用if/for/ While和一个小的主体是可以的,但是对于多子句语句就不要这样做了。还要避免折叠这样长的线条!
最好别这样:
if foo == 'blah': do_blah_thing()
for x in lst: total += x
while t < 10: t = delay()
绝对别这样:
if foo == 'blah': do_blah_thing()
else: do_non_blah_thing()
try: something()
finally: cleanup()
do_one(); do_two(); do_three(long, argument,
list, like, this)
if foo == 'blah': one(); two(); three()
何时使用尾随逗号(When to Use Trailing Commas)
- 结尾逗号通常是可选的,但在构成一个元素的元组时是强制性的(在Python 2中,它们具有打印语句的语义)。为了清楚起见,建议用(技术上多余的)括号将后者括起来。
正例:
FILES = ('setup.cfg',)
反例:
FILES = 'setup.cfg',
- 如果结尾的逗号多余,则在使用版本控制系统时,当值,参数或导入项的列表预计会随着时间扩展时,它们通常会很有用。 模式是将每个值(等)单独放在一行上,始终添加尾随逗号,并在下一行上添加右括号/括号/括号。 但是,在与结束定界符相同的行上使用尾随逗号是没有意义的(在上述单例元组的情况下除外)。
正例:
FILES = [
'setup.cfg',
'tox.ini',
]
initialize(FILES,
error=True,
)
反例:
FILES = ['setup.cfg', 'tox.ini',]
initialize(FILES, error=True,)
注释(Comments)
- 与代码矛盾的注释比没有注释更糟糕。 当代码更改时,始终优先考虑更新注释!
- 评论应该是完整的句子。 第一个单词应大写,除非它是一个以小写字母开头的标识符(请勿更改标识符的大小写!)。
- 整体注释通常由一个或多个完整句子组成的段落组成,每个句子以句点结尾。
- 在多句注释中,除了最后一句之后,您应该在句子结尾句后使用两个空格。
- 编写英语时,请遵循Strunk and White。
- 来自非英语国家的Python编码人员:请用英语写您的注释,除非您有120%的把握确保不会说这种语言的人不会阅读该代码。
块注释(Block Comments)
- 块注释通常适用于其后的一些(或全部)代码,并且缩进到与该代码相同的级别。 块注释的每一行都以#和一个空格开头(除非注释中的文本是缩进的)。
- 块注释中的段落由包含单个#的行分隔。
行内注释(Inline Comments)
- 谨慎使用内联注释。
- 内联注释是与语句在同一行上的注释。 内联注释应与语句至少分隔两个空格。 它们应以#和单个空格开头。
- 内联注释是不必要的,并且如果它们表明显而易见,则实际上会分散注意力。 不要这样做:
反例:
x = x + 1 # Increment x
但是有时候,这很有用:
正例:
x = x + 1 # Compensate for border
文档字符串(Documentation Strings)
- 编写好的文档说明(也叫“docstrings”)的约定在PEP 257中永恒不变。
- 要为所有的公共模块、函数、类和方法编写文档说明。非公共的方法没有必要,但是应该有一个描述方法具体作用的注释。这个注释应该在def那一行之后。
-
PEP 257描述了良好的文档字符串的约定。 特别需要注意的是,多行文档字符串结尾的三引号
"""
应自成一行:
"""Return a foobang
Optional plotz says to frobnicate the bizbaz first.
"""
- 对于一个线性文档字符串,请保持结束的
"""
在同一行。
命名规范(Naming Conventions)
Python库的命名约定有点混乱,因此我们永远都无法做到完全一致-尽管如此,这是当前推荐的命名标准。 新的模块和软件包(包括第三方框架)应按照这些标准编写,但是如果现有库具有不同的样式,则首选内部一致性。
最重要的原则(Overriding Principle)
那些暴露给用户的API接口的命名,应该遵循反映使用场景
而不是实现的原则。
描述:命名风格(Descriptive: Naming Styles)
- 有很多不同的命名样式。 能够独立于它们的用途来识别正在使用的命名样式。
- 通常区分以下命名样式:
2.1 b(单个小写字母)
2.2 B(单个大写字母)
2.3 lowercase 小写字母
2.4 lower_case_with_underscores 使用下划线分隔的小写字母
2.5 UPPERCASE 大写字母
2.6 UPPER_CASE_WITH_UNDERSCORES 使用下划线分隔的大写字母
2.7 CapitalizedWords(或者叫 CapWords,或者叫CamelCase 驼峰命名法 —— 这么命名是因为字母看上去有起伏的外观5)。有时候也被称为StudlyCaps。
注意:
当在首字母大写的风格中用到缩写时,所有缩写的字母用大写,因此,HTTPServerError 比 HttpServerError 好。
2.8 mixedCase(不同于首字母大写,第一个单词的首字母小写)
2.9 Capitalized_Words_With_Underscores(巨丑无比!) - 也有用唯一的短前缀把相关命名组织在一起的方法。这在Python中不常用,但还是提一下。比如,os.stat()函数中包含类似以st_mode,st_size,st_mtime这种传统命名方式命名的变量。(这么做是为了与 POSIX 系统的调用一致,以帮助程序员熟悉它。)
- X11库的所有公共函数都加了前缀X。在Python里面没必要这么做,因为属性和方法在调用的时候都会用类名做前缀,函数名用模块名做前缀。
- 另外,下面这种用前缀或结尾下划线的特殊格式是被认可的(通常和一些约定相结合):
5.1_single_leading_underscore:
(单下划线开头)弱“内部使用”指示器。比如 from M import * 是不会导入以下划线开始的对象的。
5.2single_trailing_underscore_:
(单下划线结尾)这是避免和Python内部关键词冲突的一种约定,比如:Tkinter.Toplevel(master, class_=’ClassName’)
5.3__double_leading_underscore:
(双下划线开头)当这样命名一个类的属性时,调用它的时候名字会做矫正(在类FooBar中,__boo变成了_FooBar__boo;见下文)。
5.4__double_leading_and_trailing_underscore__:
(双下划线开头,双下划线结尾)“magic”对象或者存在于用户控制的命名空间内的属性,例如:__init__
,__import__
或者__file__
。除了作为文档之外,永远不要命这样的名。
约定俗成:命名规范(Prescriptive: Naming Conventions)
应避免的名字(Names to Avoid)
- 永远不要使用字母‘l’(小写的L),‘O’(大写的O),或者‘I’(大写的I)作为单字符变量名。
- 在有些字体里,这些字符无法和数字0和1区分,如果想用‘l’,用‘L’代替。
ASCII 兼容性(ASCII Compatibility)
包和模块名(Package and Module Names)
- 模块应使用简短的全小写名称。 如果模块名称可以提高可读性,则可以在模块名称中使用下划线。 尽管不鼓励使用下划线,但Python软件包也应使用短的全小写名称。
- 当用C或C ++编写的扩展模块具有随附的Python模块提供更高级别的接口(例如,面向对象的接口)时,C / C ++模块具有下划线(例如_socket)。
类名称(Class Names)
- 类名通常应使用CapWords约定。
- 在接口被记录并主要用作可调用函数的情况下,可以代替使用函数的命名约定。
- 请注意,内置名称有一个单独的约定:大多数内置名称是单个单词(或两个单词一起运行),而CapWords约定仅用于异常名称和内置常量。
类型变量名称(Type Variable Names)
- 在PEP 484中引入的类型变量的名称通常应使用CapWord,而应使用短名称:T,AnyStr,Num。 建议将后缀_co或_contra分别添加到用于声明协变或相反行为的变量中:
from typing import TypeVar
VT_co = TypeVar('VT_co', covariant=True)
KT_contra = TypeVar('KT_contra', contravariant=True)
异常名称(Exception Names)
- 因为异常应该是类,所以此处使用类命名约定。 但是,您应该在异常名称上使用后缀“ Error”(如果异常实际上是一个错误)。
全局变量名称(Global Variable Names)
- (我们希望这些变量只能在一个模块内使用。)约定与函数的约定大致相同。
- 设计用于通过M import *使用的模块应使用all机制以防止导出全局变量,或使用较早的约定在此类全局变量前加下划线(您可能需要这样做以表明这些全局变量是“非公共模块” ”)。
函数和变量名称(Function and Variable Names)
- 函数名称应小写,必要时用下划线分隔单词,以提高可读性。
- 变量名与函数名遵循相同的约定。
- 仅在已经是主流样式(例如threading.py)的上下文中才允许使用mixedCase,以保持向后兼容性。
函数和方法名称(Function and Method Arguments)
- 始终将self作为实例方法的第一个参数。
- 始终对类方法的第一个参数使用cls。
- 如果函数参数的名称与保留关键字发生冲突,通常最好在末尾附加一个下划线,而不要使用缩写或拼写错误。 因此,class_优于clss。 (也许最好通过使用同义词来避免此类冲突。)
方法名称和实例变量(Method Names and Instance Variables)
- 使用函数命名规则:小写字母,必要时用下划线分隔单词,以提高可读性。
- 仅对非公共方法和实例变量使用前导下划线。
- 为避免名称与子类冲突,请使用两个前导下划线来调用Python的名称处理规则。
- Python用类名来修饰这些名称:如果Foo类具有名为a的属性,则Foo . a无法访问它。 (坚持的用户仍可以通过调用Foo._Foo__a来获得访问权限。)通常,双引号下划线仅应用于避免名称与设计为子类的类中的属性发生冲突。
注意:
关于__name的使用存在一些争议(请参见下文)。
常量(Constants)
- 常量通常在模块级别定义,并以所有大写字母书写,并用下划线分隔单词。 示例包括MAX_OVERFLOW和TOTAL。
继承的设计(Designing for Inheritance)
- 始终要考虑到一个类的方法和实例变量(统称:属性)应该是共有还是非共有。如果存在疑问,那就选非共有;因为将一个非共有变量转为共有比反过来更容易。
- 公共属性是那些与类无关的客户使用的属性,并承诺避免向后不兼容的更改。非共有属性是那些不打算让第三方使用的属性;你不需要承诺非共有属性不会被修改或被删除。
- 我们不使用“私有(private)”这个说法,是因为在Python中目前还没有真正的私有属性(为了避免大量不必要的常规工作)。
- 另一种属性作为子类API的一部分(在其他语言中通常被称为“protected”)。有些类是专为继承设计的,用来扩展或者修改类的一部分行为。当设计这样的类时,要谨慎决定哪些属性时公开的,哪些是作为子类的API,哪些只能在基类中使用。
- 贯彻这样的思想,一下是一些让代码Pythonic的准则:
5.1 公共属性不应该有前缀下划线。
5.2 如果公共属性名和关键字冲突,在属性名之后增加一个下划线。这比缩写和随意拼写好很多。(然而,尽管有这样的规则,在作为参数或者变量时,‘cls’是表示‘类’最好的选择,特别是作为类方法的第一个参数。)
注意1:参考之前的类方法参数命名建议
5.3 对于单一的共有属性数据,最好直接对外暴露它的变量名,而不是通过负责的 存取器(accessor)/突变(mutator) 方法。请记住,如果你发现一个简单的属性需要成长为一个功能行为,那么Python为这种将来会出现的扩展提供了一个简单的途径。在这种情况下,使用属性去隐藏属性数据访问背后的逻辑。
注意1:属性只在new-style类中起作用。
注意2:尽管功能方法对于类似缓存的负面影响比较小,但还是要尽量避免。
注意3:属性标记会让调用者认为开销(相当的)小,避免用属性做开销大的计算。
5.4 如果你的类打算用来继承的话,并且这个类里有不希望子类使用的属性,就要考虑使用双下划线前缀并且没有后缀下划线的命名方式。这会调用Python的命名转换算法,将类的名字加入到属性名里。这样做可以帮助避免在子类中不小心包含了相同的属性名而产生的冲突。
注意1:只有类名才会整合进属性名,如果子类的属性名和类名和父类都相同,那么你还是会有命名冲突的问题。
注意2:命名转换会在某些场景使用起来不太方便,例如调试,getattr()。然而命名转换的算法有很好的文档说明并且很好操作。
注意3:不是所有人都喜欢命名转换。尽量避免意外的名字冲突和潜在的高级调用。
公共和内部接口(Public and Internal Interfaces)
- 任何向后兼容性保证都仅适用于公共接口。因此,重要的是用户能够清楚地区分公共接口和内部接口。
- 除非文档明确声明它们是临时接口或内部接口不受通常的向后兼容性保证,否则已说明文件的接口被视为公共接口。所有未记录的接口都应假定为内部接口。
- 为了更好地支持自省,模块应使用
__all__
属性在其公共API中显式声明名称。将__all__
设置为空列表表示该模块没有公共API。 - 即使适当地设置了
__all__
,内部接口(包,模块,类,函数,属性或其他名称)仍应以单个下划线作为前缀。 - 如果任何包含名称空间(包,模块或类)的内部接口都被视为内部接口,则该接口也被视为内部接口。
- 导入的名称应始终被视为实现细节。除非其他模块是包含模块的API中明确记录的一部分,否则其他模块不得依赖对此类导入名称的间接访问,例如os.path或从子模块公开功能的软件包的
__init__
模块。
编程建议(Programming Recommendations)
- 比如,不要依赖于在CPython中高效的内置字符连接语句 a += b 或者 a = a + b。这种优化甚至在CPython中都是脆弱的(它只适用于某些类型)并且没有出现在不使用引用计数的实现中。在性能要求比较高的库中,可以种 ”.join() 代替。这可以确保字符关联在不同的实现中都可以以线性时间发生。
- 和像None这样的单例对象进行比较的时候应该始终用 is 或者 is not,永远不要用等号运算符。
另外,如果你在写 if x 的时候,请注意你是否表达的意思是 if x is not None。举个例子,当测试一个默认值为None的变量或者参数是否被设置为其他值的时候。这个其他值应该是在上下文中能成为bool类型false的值。 - 使用 is not 运算符,而不是 not … is 。虽然这两种表达式在功能上完全相同,但前者更易于阅读,所以优先考虑。
正例:
if foo is not None:
反例:
if not foo is None:
- 当使用富比较(rich comparisons,一种复杂的对象间比较的新机制,允许返回值不为-1,0,1)实现排序操作的时候,最好实现全部的六个操作符(
__eq__
,__ne__
,__lt__
,__gt__
,__ge__
)而不是依靠其他的代码去实现特定的比较。
为了最大程度减少这一过程的开销, functools.total_ordering() 修饰符提供了用于生成缺少的比较方法的工具。
PEP 207 指出Python实现了反射机制。因此,解析器会将 y > x 转变为 x < y,将 y >= x 转变为 x <= y,也会转换x == y 和 x != y的参数。sort() 和 min()方法确保使用<操作符,max()使用>操作符。然而,最好还是实现全部六个操作符,以免在其他地方出现冲突。 - 始终使用def表达式,而不是通过赋值语句将lambda表达式绑定到一个变量上。
正例:
def f(x): return 2*x
反例:
f = lambda x: 2*x
第一个形式意味着生成的函数对象的名称是“f”而不是泛型“< lambda >”。
这在回溯和字符串显示的时候更有用。赋值语句的使用消除了lambda表达式优于显式def表达式的唯一优势
(即lambda表达式可以内嵌到更大的表达式中)。
- 从Exception继承异常,而不是BaseException。直接继承BaseException的异常适用于几乎不用来捕捉的异常。
设计异常的等级,要基于扑捉异常代码的需要,而不是异常抛出的位置。以编程的方式去回答“出了什么问题?”,而不是只是确认“出现了问题”(内置异常结构的例子参考 PEP 3151 )
类的命名规范适用于这里,但是你需要添加一个“Error”的后缀到你的异常类,如果异常是一个Error的话。非本地流控制或者其他形式的信号的非错误异常不需要特殊的后缀。 - 适当地使用异常链接。在Python 3里,为了不丢失原始的根源,可以显式指定“raise X from Y”作为替代。
当故意替换一个内部异常时(Python 2 使用“raise X”, Python 3.3 之后 使用 “raise X from None”),确保相关的细节转移到新的异常中(比如把AttributeError转为KeyError的时候保留属性名,或者将原始异常信息的文本内容内嵌到新的异常中)。 - 在Python 2中抛出异常时,使用 rasie ValueError(‘message’) 而不是用老的形式 raise ValueError, ‘message’。
第二种形式在Python3 的语法中不合法
使用小括号,意味着当异常里的参数非常长,或者包含字符串格式化的时候,不需要使用换行符。 - 当捕获到异常时,如果可以的话写上具体的异常名,而不是只用一个except: 块。
正例:
try:
import platform_specific_module
except ImportError:
platform_specific_module = None
如果只有一个except: 块将会捕获到SystemExit和KeyboardInterrupt异常,
这样会很难通过Control-C中断程序,而且会掩盖掉其他问题。如果你想捕获所有指示程序出错的异常,
使用 except Exception: (只有except等价于 except BaseException:)。
两种情况不应该只使用‘excpet’块:
1\. 如果异常处理的代码会打印或者记录log;至少让用户知道发生了一个错误。
2\. 如果代码需要做清理工作,使用 raise..try…finally 能很好处理这种情况并且能让异常继续上浮。
- 当给捕捉的异常绑定一个名字时,推荐使用在Python 2.6中加入的显式命名绑定语法:
正例:
try:
process_data()
except Exception as exc:
raise DataProcessingFailedError(str(exc))
为了避免和原来基于逗号分隔的语法出现歧义,Python3只支持这一种语法。
- 当捕捉操作系统的错误时,推荐使用Python 3.3 中errno内定数值指定的异常等级。
- 另外,对于所有的 try/except 语句块,在try语句中只填充必要的代码,这样能避免掩盖掉bug。
推荐:
正例:
try:
value = collection[key]
except KeyError:
return key_not_found(key)
else:
return handle_value(value)
反例:
try:
# Too broad!
return handle_value(collection[key])
except KeyError:
# Will also catch KeyError raised by handle_value()
return key_not_found(key)
- 当代码片段局部使用了某个资源的时候,使用with 表达式来确保这个资源使用完后被清理干净。用try/finally也可以。
- 无论何时获取和释放资源,都应该通过单独的函数或方法调用上下文管理器。举个例子:
正例:
with conn.begin_transaction():
do_stuff_in_transaction(conn)
反例:
with conn:
do_stuff_in_transaction(conn)
第二个例子没有提供任何信息去指明__enter__和__exit__方法在事务之后做出了关闭连接之外的其他事情。
这种情况下,明确指明非常重要。
- 返回的语句保持一致。函数中的返回语句都应该返回一个表达式,或者都不返回。如果一个返回语句需要返回一个表达式,那么在没有值可以返回的情况下,需要用 return None 显式指明,并且在函数的最后显式指定一条返回语句(如果能跑到那的话)。
正例:
def foo(x):
if x >= 0:
return math.sqrt(x)
else:
return None
def bar(x):
if x < 0:
return None
return math.sqrt(x)
反例:
def foo(x):
if x >= 0:
return math.sqrt(x)
def bar(x):
if x < 0:
return
return math.sqrt(x)
- 使用字符串方法代替字符串模块。
字符串方法总是更快,并且和unicode字符串分享相同的API。如果需要兼容Python2.0之前的版本可以不用考虑这个规则。 - 使用 ”.startswith() 和 ”.endswith() 代替通过字符串切割的方法去检查前缀和后缀。
startswith()和endswith()更干净,出错几率更小。比如:
正例: if foo.startswith('bar'):
反例: if foo[:3] == 'bar':
- 对象类型的比较应该用isinstance()而不是直接比较type。
正例: if isinstance(obj, int):
反例: if type(obj) is type(1):
- 当检查一个对象是否为string类型时,记住,它也有可能是unicode string!在Python2中,str和unicode都有相同的基类:basestring,所以你可以这样:
if isinstance(obj, basestring):
注意,在Python3中,unicode和basestring都不存在了(只有str)
并且bytes类型的对象不再是string类型的一种(它是整数序列)
- 对于序列来说(strings,lists,tuples),可以使用空序列为false的情况。
正例:
if not seq:
if seq:
反例:
if len(seq):
if not len(seq):
- 书写字符串时不要依赖单词结尾的空格,这样的空格在视觉上难以区分,有些编辑器会自动去掉他们(比如 reindent.py (译注:re indent 重新缩进))
- 不要用 == 去和True或者False比较:
正例: if greeting:
反例: if greeting == True:
更糟: if greeting is True:
- 不鼓励在try … final套件中使用流控制语句return / break / continue …最后,在该方法中,流控制语句将跳到finally套件之外。 这是因为这样的语句将隐式取消通过final套件传播的任何活动异常。
反例:
def foo():
try:
1 / 0
finally:
return 42
函数注释(Function Annotations)
随着PEP 484的引入,功能型注释的风格规范有些变化。
- 为了向前兼容,在Python3代码中的功能注释应该使用 PEP 484的语法规则。(在前面的章节中对注释有格式化的建议。)
- 不再鼓励使用之前在PEP中推荐的实验性样式。
- 然而,在stdlib库之外,在PEP 484中的实验性规则是被鼓励的。比如用PEP 484的样式标记大型的第三方库或者应用程序,回顾添加这些注释是否简单,并观察是否增加了代码的可读性。
- Python的标准库代码应该保守使用这种注释,但新的代码或者大型的重构可以使用这种注释。
- 如果代码希望对功能注释有不同的用途,建议在文件的顶部增加一个这种形式的注释:
# type: ignore
这会告诉检查器忽略所有的注释。(在 PEP 484中可以找到从类型检查器禁用投诉的更细粒度的方法。)
- 像linters一样,类型检测器是可选的可独立的工具。默认情况下,Python解释器不应该因为类型检查而发出任何消息,也不应该基于注释改变它们的行为。
- 不想使用类型检测的用户可以忽略他们。然而,第三方库的用户可能希望在这些库上运行类型检测。为此, PEP 484 建议使用存根文件类型:.pyi文件,这种文件类型相比于.py文件会被类型检测器读取。存根文件可以和库一起,或者通过typeshed repo6独立发布(通过库作者的许可)5
- 对于需要向后兼容的代码,可以以注释的形式添加功能型注释。参见PEP 484的相关部分6。
变量注释(Variable Annotations)
- PEP 526引入了变量注释。 针对它们的样式建议与上述功能注释类似:
- 模块级变量,类和实例变量以及局部变量的注释应在冒号后面有一个空格。
- 冒号前不应有空格。
- 如果赋值有一个右手边,则等号在两边应恰好有一个空格。
正例:
code: int
class Point:
coords: Tuple[int, int]
label: str = '<unknown>'
反例:
code:int # No space after colon
code : int # Space before colon
class Test:
result: int=0 # No spaces around equality sign
参考实例
微信开发包,python实现, wechat_sdk开发,可做 规范参考和类的设计学习
from __future__ import unicode_literals
import time
from wechat_sdk.lib.crypto import BasicCrypto
from wechat_sdk.lib.request import WechatRequest
from wechat_sdk.exceptions import NeedParamError
from wechat_sdk.utils import disable_urllib3_warning
class WechatConf(object):
""" WechatConf 配置类
该类将会存储所有和微信开发相关的配置信息, 同时也会维护配置信息的有效性.
"""
def __init__(self, **kwargs):
"""
:param kwargs: 配置信息字典, 可用字典 key 值及对应解释如下:
'token': 微信 Token
'appid': App ID
'appsecret': App Secret
'encrypt_mode': 加解密模式 ('normal': 明文模式, 'compatible': 兼容模式, 'safe': 安全模式(默认))
'encoding_aes_key': EncodingAESKey 值 (传入此值必须保证同时传入 token, appid, 否则抛出异常)
'access_token_getfunc': access token 获取函数 (用于单机及分布式环境下, 具体格式参见文档)
'access_token_setfunc': access token 写入函数 (用于单机及分布式环境下, 具体格式参见文档)
'access_token_refreshfunc': access token 刷新函数 (用于单机及分布式环境下, 具体格式参见文档)
'access_token': 直接导入的 access token 值, 该值需要在上一次该类实例化之后手动进行缓存并在此处传入, 如果不
传入, 将会在需要时自动重新获取 (传入 access_token_getfunc 和 access_token_setfunc 函数
后将会自动忽略此处的传入值)
'access_token_expires_at': 直接导入的 access token 的过期日期, 该值需要在上一次该类实例化之后手动进行缓存
并在此处传入, 如果不传入, 将会在需要时自动重新获取 (传入 access_token_getfunc
和 access_token_setfunc 函数后将会自动忽略此处的传入值)
'jsapi_ticket_getfunc': jsapi ticket 获取函数 (用于单机及分布式环境下, 具体格式参见文档)
'jsapi_ticket_setfunc': jsapi ticket 写入函数 (用于单机及分布式环境下, 具体格式参见文档)
'jsapi_ticket_refreshfunc': jsapi ticket 刷新函数 (用于单机及分布式环境下, 具体格式参见文档)
'jsapi_ticket': 直接导入的 jsapi ticket 值, 该值需要在上一次该类实例化之后手动进行缓存并在此处传入, 如果不
传入, 将会在需要时自动重新获取 (传入 jsapi_ticket_getfunc 和 jsapi_ticket_setfunc 函数
后将会自动忽略此处的传入值)
'jsapi_ticket_expires_at': 直接导入的 jsapi ticket 的过期日期, 该值需要在上一次该类实例化之后手动进行缓存
并在此处传入, 如果不传入, 将会在需要时自动重新获取 (传入 jsapi_ticket_getfunc
和 jsapi_ticket_setfunc 函数后将会自动忽略此处的传入值)
'partnerid': 财付通商户身份标识, 支付权限专用
'partnerkey': 财付通商户权限密钥 Key, 支付权限专用
'paysignkey': 商户签名密钥 Key, 支付权限专用
'checkssl': 是否检查 SSL, 默认不检查 (False), 可避免 urllib3 的 InsecurePlatformWarning 警告
:return:
"""
self.__request = WechatRequest()
if kwargs.get('checkssl') is not True:
disable_urllib3_warning() # 可解决 InsecurePlatformWarning 警告
self.__token = kwargs.get('token')
self.__appid = kwargs.get('appid')
self.__appsecret = kwargs.get('appsecret')
self.__encrypt_mode = kwargs.get('encrypt_mode', 'safe')
self.__encoding_aes_key = kwargs.get('encoding_aes_key')
self.__crypto = None
self._update_crypto()
self.__access_token_getfunc = kwargs.get('access_token_getfunc')
self.__access_token_setfunc = kwargs.get('access_token_setfunc')
self.__access_token_refreshfunc = kwargs.get('access_token_refreshfunc')
self.__access_token = kwargs.get('access_token')
self.__access_token_expires_at = kwargs.get('access_token_expires_at')
self.__jsapi_ticket_getfunc = kwargs.get('jsapi_ticket_getfunc')
self.__jsapi_ticket_setfunc = kwargs.get('jsapi_ticket_setfunc')
self.__jsapi_ticket_refreshfunc = kwargs.get('jsapi_ticket_refreshfunc')
self.__jsapi_ticket = kwargs.get('jsapi_ticket')
self.__jsapi_ticket_expires_at = kwargs.get('jsapi_ticket_expires_at')
self.__partnerid = kwargs.get('partnerid')
self.__partnerkey = kwargs.get('partnerkey')
self.__paysignkey = kwargs.get('paysignkey')
@property
def token(self):
""" 获取当前 Token """
self._check_token()
return self.__token
@token.setter
def token(self, token):
""" 设置当前 Token """
self.__token = token
self._update_crypto() # 改动 Token 需要重新更新 Crypto
@property
def appid(self):
""" 获取当前 App ID """
return self.__appid
@property
def appsecret(self):
""" 获取当前 App Secret """
return self.__appsecret
def set_appid_appsecret(self, appid, appsecret):
""" 设置当前 App ID 及 App Secret"""
self.__appid = appid
self.__appsecret = appsecret
self._update_crypto() # 改动 App ID 后需要重新更新 Crypto
@property
def encoding_aes_key(self):
""" 获取当前 EncodingAESKey """
return self.__encoding_aes_key
@encoding_aes_key.setter
def encoding_aes_key(self, encoding_aes_key):
""" 设置当前 EncodingAESKey """
self.__encoding_aes_key = encoding_aes_key
self._update_crypto() # 改动 EncodingAESKey 需要重新更新 Crypto
@property
def encrypt_mode(self):
return self.__encrypt_mode
@encrypt_mode.setter
def encrypt_mode(self, encrypt_mode):
""" 设置当前加密模式 """
self.__encrypt_mode = encrypt_mode
self._update_crypto()
@property
def crypto(self):
""" 获取当前 Crypto 实例 """
return self.__crypto
@property
def access_token(self):
""" 获取当前 access token 值, 本方法会自行维护 access token 有效性 """
self._check_appid_appsecret()
if callable(self.__access_token_getfunc):
self.__access_token, self.__access_token_expires_at = self.__access_token_getfunc()
if self.__access_token:
now = time.time()
if self.__access_token_expires_at - now > 60:
return self.__access_token
self.grant_access_token() # 从腾讯服务器获取 access token 并更新
return self.__access_token
@property
def jsapi_ticket(self):
""" 获取当前 jsapi ticket 值, 本方法会自行维护 jsapi ticket 有效性 """
self._check_appid_appsecret()
if callable(self.__jsapi_ticket_getfunc):
self.__jsapi_ticket, self.__jsapi_ticket_expires_at = self.__jsapi_ticket_getfunc()
if self.__jsapi_ticket:
now = time.time()
if self.__jsapi_ticket_expires_at - now > 60:
return self.__jsapi_ticket
self.grant_jsapi_ticket() # 从腾讯服务器获取 jsapi ticket 并更新
return self.__jsapi_ticket
@property
def partnerid(self):
""" 获取当前财付通商户身份标识 """
return self.__partnerid
@property
def partnerkey(self):
""" 获取当前财付通商户权限密钥 Key """
return self.__partnerkey
@property
def paysignkey(self):
""" 获取商户签名密钥 Key """
return self.__paysignkey
def grant_access_token(self):
"""
获取 access token 并更新当前配置
:return: 返回的 JSON 数据包 (传入 access_token_refreshfunc 参数后返回 None)
"""
self._check_appid_appsecret()
if callable(self.__access_token_refreshfunc):
self.__access_token, self.__access_token_expires_at = self.__access_token_refreshfunc()
return
response_json = self.__request.get(
url="https://api.weixin.qq.com/cgi-bin/token",
params={
"grant_type": "client_credential",
"appid": self.__appid,
"secret": self.__appsecret,
},
access_token=self.__access_token
)
self.__access_token = response_json['access_token']
self.__access_token_expires_at = int(time.time()) + response_json['expires_in']
if callable(self.__access_token_setfunc):
self.__access_token_setfunc(self.__access_token, self.__access_token_expires_at)
return response_json
def grant_jsapi_ticket(self):
"""
获取 jsapi ticket 并更新当前配置
:return: 返回的 JSON 数据包 (传入 jsapi_ticket_refreshfunc 参数后返回 None)
"""
self._check_appid_appsecret()
if callable(self.__jsapi_ticket_refreshfunc):
self.__jsapi_ticket, self.__jsapi_ticket_expires_at = self.__jsapi_ticket_refreshfunc()
return
response_json = self.__request.get(
url="https://api.weixin.qq.com/cgi-bin/ticket/getticket",
params={
"type": "jsapi",
},
access_token=self.access_token,
)
self.__jsapi_ticket = response_json['ticket']
self.__jsapi_ticket_expires_at = int(time.time()) + response_json['expires_in']
if callable(self.__jsapi_ticket_setfunc):
self.__jsapi_ticket_setfunc(self.__jsapi_ticket, self.__jsapi_ticket_expires_at)
return response_json
def get_access_token(self):
"""
获取 Access Token 及 Access Token 过期日期, 仅供缓存使用, 如果希望得到原生的 Access Token 请求数据请使用 :func:`grant_token`
**仅为兼容 v0.6.0 以前版本使用, 自行维护 access_token 请使用 access_token_setfunc 和 access_token_getfunc 进行操作**
:return: dict 对象, key 包括 `access_token` 及 `access_token_expires_at`
"""
self._check_appid_appsecret()
return {
'access_token': self.access_token,
'access_token_expires_at': self.__access_token_expires_at,
}
def get_jsapi_ticket(self):
"""
获取 Jsapi Ticket 及 Jsapi Ticket 过期日期, 仅供缓存使用, 如果希望得到原生的 Jsapi Ticket 请求数据请使用 :func:`grant_jsapi_ticket`
**仅为兼容 v0.6.0 以前版本使用, 自行维护 jsapi_ticket 请使用 jsapi_ticket_setfunc 和 jsapi_ticket_getfunc 进行操作**
:return: dict 对象, key 包括 `jsapi_ticket` 及 `jsapi_ticket_expires_at`
"""
self._check_appid_appsecret()
return {
'jsapi_ticket': self.jsapi_ticket,
'jsapi_ticket_expires_at': self.__jsapi_ticket_expires_at,
}
def _check_token(self):
"""
检查 Token 是否存在
:raises NeedParamError: Token 参数没有在初始化的时候提供
"""
if not self.__token:
raise NeedParamError('Please provide Token parameter in the construction of class.')
def _check_appid_appsecret(self):
"""
检查 AppID 和 AppSecret 是否存在
:raises NeedParamError: AppID 或 AppSecret 参数没有在初始化的时候完整提供
"""
if not self.__appid or not self.__appsecret:
raise NeedParamError('Please provide app_id and app_secret parameters in the construction of class.')
def _update_crypto(self):
"""
根据当前配置内容更新 Crypto 类
"""
if self.__encrypt_mode in ['compatible', 'safe'] and self.__encoding_aes_key is not None:
if self.__token is None or self.__appid is None:
raise NeedParamError('Please provide token and appid parameters in the construction of class.')
self.__crypto = BasicCrypto(self.__token, self.__encoding_aes_key, self.__appid)
else:
self.__crypto = None
参考:https://blog.csdn.net/cn_1937/article/details/90756259
参考:https://blog.csdn.net/Happy_Sunshine_Boy/article/details/103452635?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_title~default-0.pc_relevant_paycolumn_v3&spm=1001.2101.3001.4242.1&utm_relevant_index=3#_937