diff --git a/google-python-styleguide/LICENSE b/google-python-styleguide/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/google-python-styleguide/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/google-python-styleguide/background.rst b/google-python-styleguide/background.rst index 7ab344e..0492124 100644 --- a/google-python-styleguide/background.rst +++ b/google-python-styleguide/background.rst @@ -1,6 +1,8 @@ 背景 ================================ -Python 是 Google主要的脚本语言。这本风格指南主要包含的是针对python的编程准则。 +Python 是谷歌使用的重要动态语言。这本风格指南列举了 Python 程序应采纳和避免的风格。 -为帮助读者能够将代码准确格式化,我们提供了针对 `Vim的配置文件 `_ 。对于Emacs用户,保持默认设置即可。许多团队使用 `yapf `_ 作为自动格式化工具以避免格式不一致。 +为帮助你正确地格式化代码,我们创建了 `Vim的配置文件 `_ 。对于Emacs用户,保持默认设置即可。 + +许多团队采用 `Black `_ 和 `Pyink `_ 作为自动格式化工具,以避免格式上的争论。 diff --git a/google-python-styleguide/index.rst b/google-python-styleguide/index.rst index 06f0b18..5cc6097 100644 --- a/google-python-styleguide/index.rst +++ b/google-python-styleguide/index.rst @@ -1,8 +1,6 @@ 扉页 ================================ -:版本: 2.6 - :原作者: .. line-block:: @@ -13,14 +11,19 @@ Matt Smart Mike Shields + 详细作者列表请参见英文原版的 Git 仓库. + :翻译: .. line-block:: `guoqiao `_ v2.19 `xuxinkun `_ v2.59 `captainfffsama `_ v2.6 + `楼宇 `_ 2023 年 4 月 5 日更新 :项目主页: - - `Google Style Guide `_ + - `Google Style Guide (英文原版) `_ - `Google 开源项目风格指南 - 中文版 `_ +:协议: + Python风格指南的源代码采用Apache License 2.0协议, 文本内容采用 `CC-BY 3.0 `_ 协议. diff --git a/google-python-styleguide/python_language_rules.rst b/google-python-styleguide/python_language_rules.rst index 8a451df..f2504bf 100644 --- a/google-python-styleguide/python_language_rules.rst +++ b/google-python-styleguide/python_language_rules.rst @@ -5,70 +5,73 @@ Lint -------------------- .. tip:: - 使用该 `pylintrc `_ 对你的代码运行pylint - + 用 `pylintrc `_ 运行 pylint,以检查你的代码. + 定义: - pylint是一个在Python源代码中查找bug的工具. 对于C和C++这样的不那么动态的(译者注: 原文是less dynamic)语言, 这些bug通常由编译器来捕获. 由于Python的动态特性, 有些警告可能不对. 不过伪告警应该很少. + pylint 是在 Python 代码中寻找 bug 和格式问题的工具. 它寻找的问题就像 C 和 C++ 这些更静态的(译者注: 原文是less dynamic)语言中编译器捕捉的问题. 出于Python的动态特性, 部分警告可能有误. 不过, 误报应该不常见. 优点: - 可以捕获容易忽视的错误, 例如输入错误, 使用未赋值的变量等. + 可以发现疏忽, 例如拼写错误, 使用未赋值的变量等. 缺点: - pylint不完美. 要利用其优势, 我们有时侯需要: a) 围绕着它来写代码 b) 抑制其告警 c) 改进它, 或者d) 忽略它. + pylint 不完美. 要利用其优势, 我们有时侯需要: a) 绕过它 b) 抑制它的警告 或者 c) 改进它. 结论: - 确保对你的代码运行pylint. - 抑制不准确的警告,以便能够将其他警告暴露出来。你可以通过设置一个行注释来抑制警告. 例如: - - .. code-block:: python - - dict = 'something awful' # Bad Idea... pylint: disable=redefined-builtin - -   pylint警告是以符号名(如 ``empty-docstring`` )来标识的.google特定的警告则是以``g-``开头. - - 如果警告的符号名不够见名知意,那么请对其增加一个详细解释。 - - 采用这种抑制方式的好处是我们可以轻松查找抑制并回顾它们. - - 你可以使用命令 ``pylint --list-msgs`` 来获取pylint告警列表. 你可以使用命令 ``pylint --help-msg=C6409`` , 以获取关于特定消息的更多信息. - - 相比较于之前使用的 ``pylint: disable-msg`` , 本文推荐使用 ``pylint: disable`` . - - 在函数体中 ``del`` 未使用的变量可以消除参数未使用告警.记得要加一条注释说明你为何 ``del`` 它们,注释使用"Unused"就可以,例如: - - .. code-block:: python - - def viking_cafe_order(spam, beans, eggs=None): - del beans, eggs # Unused by vikings. - return spam + spam + spam + 一定要用pylint检查你的代码. - 其他消除这个告警的方法还有使用`_`标志未使用参数,或者给这些参数名加上前缀 ``unused_``, 或者直接把它们绑定到 ``_``.但这些方法都不推荐. + 抑制不恰当的警告, 以免其他问题被警告淹没。你可以用行注释来抑制警告. 例如: + + .. code-block:: python + + def do_PUT(self): # WSGI 接口名, 所以 pylint: disable=invalid-name + ... + + pylint的警告均以符号名(如 ``empty-docstring`` )来区分. 谷歌特有的警告以 ``g-`` 为前缀. + + 如果警告的符号名不够见名知意,那么请添加注释。 + + 这种抑制方式的好处是, 我们可以轻易搜索并重新评判这些注释. + + 你可以用命令 ``pylint --list-msgs`` 来列出 pylint 的所有警告. 你可以用命令 ``pylint --help-msg=invalid-name`` 来查询某个警告的详情. + + 相较于旧的格式 ``pylint: disable-msg`` , 本文推荐使用 ``pylint: disable`` . + + 如果有“参数未使用”的警告,你可以在函数体开头删除无用的变量,以消除警告. 一定要用注释说明你为什么删除这些变量. 注明"未使用."即可. 例如: + + .. code-block:: python + + def viking_cafe_order(spam: str, beans: str, eggs: str | None = None) -> str: + del beans, eggs # 未被维京人使用. + return spam + spam + spam + (译者注:Viking 意为维京人.) + + 其他避免这种警告的常用方法还有: 用`_`作为未使用参数的名称; 给这些参数名加上前缀 ``unused_``; 或者把它们赋值给变量 ``_``. 我们允许但是不再推荐这些方法. 这会导致调用者无法通过参数名来传参,也不能保证变量确实没被引用。 导入 -------------------- .. tip:: - 仅对包和模块使用导入,而不单独导入函数或者类。`typing`模块例外。 + 使用 ``import`` 语句时, 只导入包和模块, 而不单独导入函数或者类。 定义: - 模块间共享代码的重用机制. + 用于方便模块间共享代码的重用机制. 优点: - 命名空间管理约定十分简单. 每个标识符的源都用一种一致的方式指示. x.Obj表示Obj对象定义在模块x中. + 命名空间的管理规范十分简单. 每个标识符的来源都用一致的方式来表示. ``x.Obj`` 表示 ``Obj`` 对象定义在模块 ``x`` 中. 缺点: - 模块名仍可能冲突. 有些模块名太长, 不太方便. + 模块名可能有命名冲突. 有些模块名的长度过长以至于不方便. 结论: - #. 使用 ``import x`` 来导入包和模块. + #. 用 ``import x`` 来导入包和模块. - #. 使用 ``from x import y`` , 其中x是包前缀, y是不带前缀的模块名. + #. 用 ``from x import y`` , 其中x是包前缀, y是不带前缀的模块名. - #. 使用 ``from x import y as z``, 如果两个要导入的模块都叫做y或者y太长了. + #. 在以下情况使用 ``from x import y as z``: 如果有两个模块都叫 ``y``; 如果 ``y`` 和当前模块的某个全局名称冲突; 如果 ``y`` 是长度过长的名称. - #. 仅当缩写 ``z`` 是通用缩写时才可使用 ``import y as z``.(比如 ``np`` 代表 ``numpy``.) + #. 仅当缩写 ``z`` 是标准缩写时才能使用 ``import y as z``.(比如 ``np`` 代表 ``numpy``.) - 例如, 模块 ``sound.effects.echo`` 可以用如下方式导入: + 例如, 可以用如下方式导入模块 ``sound.effects.echo``: .. code-block:: python @@ -76,36 +79,46 @@ Lint ... echo.EchoFilter(input, output, delay=0.7, atten=4) - 导入时不要使用相对名称. 即使模块在同一个包中, 也要使用完整包名. 这能帮助你避免无意间导入一个包两次. + 导入时禁止使用相对包名. 即使模块在同一个包中, 也要使用完整包名. 这能避免无意间重复导入同一个包. - 导入 ``typing`` 和 `six.moves `_ 模块时可以例外. +例外: + + 这一规定的例外是: + + #. 以下用于静态分析和类型检查的模块: + + #. ``typing`` 模块 + #. ``collections.abc`` 模块 + #. ``typing_extensions`` 模块 + + #. `six.moves `_ 模块中的重定向. 包 -------------------- .. tip:: - 使用模块的全路径名来导入每个模块 + 使用每个模块的完整路径名来导入模块. 优点: - 避免模块名冲突或是因非预期的模块搜索路径导致导入错误. 查找包更容易. + 避免模块名冲突, 或是因模块搜索路径与作者的想法不符而导入错误的包. 也更容易找到模块. 缺点: - 部署代码变难, 因为你必须复制包层次. + 部署代码更难, 因为你必须完整复刻包的层次. 在现代的部署模式下不再是问题. 结论: - 所有的新代码都应该用完整包名来导入每个模块. + 所有新的代码都应该用完整包名来导入每个模块. 应该像下面这样导入: - yes: + 正确: .. code-block:: python - # 在代码中引用完整名称 absl.flags (详细情况). + # 在代码中引用完整名称 absl.flags (详细版). import absl.flags from doctor.who import jodie - FLAGS = absl.flags.FLAGS + _FOO = absl.flags.DEFINE_string(...) .. code-block:: python @@ -113,159 +126,167 @@ Lint from absl import flags from doctor.who import jodie - FLAGS = flags.FLAGS + _FOO = flags.DEFINE_string(...) - No: (假设当前文件和 `jodie.py` 都在目录 `doctor/who/` 下) + 错误: (假设当前文件和 `jodie.py` 都在目录 `doctor/who/` 下) .. code-block:: python - # 没能清晰指示出作者想要导入的模块和最终被导入的模块. - # 实际导入的模块将取决于 sys.path. + # 没有清晰地表达作者想要导入的模块和最终导入的模块. + # 实际导入的模块取决于由外部环境控制的 sys.path. + # 那些名为 jodie 的模块中, 哪个才是作者想导入的? import jodie - 不应假定主入口脚本所在的目录就在 `sys.path` 中,虽然这种情况是存在的。当主入口脚本所在目录不在 `sys.path` 中时,代码将假设 `import jodie` 是导入的一个第三方库或者是一个名为 `jodie` 的顶层包,而不是本地的 `jodie.py` + 不能臆测 `sys.path` 包含主程序所在的目录, 即使这种环境的确存在. 因此, 代码必须认定 `import jodie` 表示的是名为 `jodie` 的第三方库或者顶层的包,而非当前目录的 `jodie.py`. 异常 -------------------- .. tip:: - 允许使用异常, 但必须小心 + 允许使用异常, 但必须谨慎使用. 定义: - 异常是一种跳出代码块的正常控制流来处理错误或者其它异常条件的方式. + 异常是一种跳出正常的控制流, 以处理错误或其它异常情况的方法. 优点: - 正常操作代码的控制流不会和错误处理代码混在一起. 当某种条件发生时, 它也允许控制流跳过多个框架. 例如, 一步跳出N个嵌套的函数, 而不必继续执行错误的代码. + 处理正常情况的控制流不会和错误处理代码混在一起. 在特定情况下, 它也能让控制流跳出多层调用帧. 例如, 一步跳出N多层嵌套的函数, 而不必逐层传递错误代码. 缺点: - 可能会导致让人困惑的控制流. 调用库时容易错过错误情况. + 可能导致控制流晦涩难懂. 调用库函数时容易忘记处理异常. 结论: - 异常必须遵守特定条件: + 使用异常时必须遵守特定要求: - #. 优先合理的使用内置异常类.比如 ``ValueError`` 指示了一个程序错误, 比如在方法需要正数的情况下传递了一个负数错误.不要使用 ``assert`` 语句来验证公共API的参数值. ``assert`` 是用来保证内部正确性的,而不是用来强制纠正参数使用.若需要使用异常来指示某些意外情况,不要用 ``assert``,用 ``raise`` 语句,例如: + #. 优先使用合适的内置异常类. 比如, 用 ``ValueError`` 表示前置条件错误 (例如给必须为正数的参数传入了负值). 不要使用 ``assert`` 语句来验证公开API的参数值. 应该用 ``assert`` 来保证内部正确性, 不应该用 ``assert`` 来纠正参数或表示意外情况. 若要用异常来表示意外情况, 应该用 ``raise``. 例如: - Yes: + 正确: .. code-block:: python - def connect_to_next_port(self, minimum): - """Connects to the next available port. + def connect_to_next_port(self, minimum: int) -> int: + """连接到下一个可用的端口. Args: - minimum: A port value greater or equal to 1024. + minimum: 一个大于等于 1024 的端口号. Returns: - The new minimum port. + 新的最小端口. Raises: - ConnectionError: If no available port is found. + ConnectionError: 没有可用的端口. """ 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(f'Min. port must be at least 1024, not {minimum}.') + # 注意这里抛出 ValueError 的情况没有在文档里说明,因为 API 的 + # 错误用法应该是未定义行为. + raise ValueError(f'最小端口号至少为 1024,不能是 {minimum}.') port = self._find_next_open_port(minimum) - if not port: + if port is None: raise ConnectionError( - f'Could not connect to service on port {minimum} or higher.') + f'未能通过 {minimum} 或更高的端口号连接到服务.') assert port >= minimum, ( - f'Unexpected port {port} when minimum was {minimum}.') + f'意外的端口号 {port}, 端口号不应小于 {minimum}.') return port - No: + 错误: .. code-block:: python - def connect_to_next_port(self, minimum): - """Connects to the next available port. + def connect_to_next_port(self, minimum: int) -> int: + """连接到下一个可用的端口. Args: - minimum: A port value greater or equal to 1024. + minimum: 一个大于等于 1024 的端口号. Returns: - The new minimum port. + 新的最小端口. """ - assert minimum >= 1024, 'Minimum port must be at least 1024.' + assert minimum >= 1024, '最小端口号至少为 1024.' port = self._find_next_open_port(minimum) assert port is not None return port - #. 模块或包应该定义自己的特定域的异常基类, 这个基类应该从内建的Exception类继承. 模块的异常基类后缀应该叫做 ``Error``. - #. 永远不要使用 ``except:`` 语句来捕获所有异常, 也不要捕获 ``Exception`` 或者 ``StandardError`` , 除非你打算重新触发该异常, 或者你已经在当前线程的最外层(记得还是要打印一条错误消息). 在异常这方面, Python非常宽容, ``except:`` 真的会捕获包括Python语法错误在内的任何错误. 使用 ``except:`` 很容易隐藏真正的bug. - #. 尽量减少try/except块中的代码量. try块的体积越大, 期望之外的异常就越容易被触发. 这种情况下, try/except块将隐藏真正的错误. - #. 使用finally子句来执行那些无论try块中有没有异常都应该被执行的代码. 这对于清理资源常常很有用, 例如关闭文件. + #. 模块或包可以定义自己的异常类型, 这些类必须继承已有的异常类. 异常类型名应该以 ``Error`` 为后缀, 并且不应该有重复 (例如 ``foo.FooError``). + #. 永远不要使用 ``except:`` 语句来捕获所有异常, 也不要捕获 ``Exception`` 或者 ``StandardError`` , 除非你想: + + #. 重新抛出异常. + #. 在程序中创造一个隔离点, 记录并抑制异常, 让异常不再继续传播. 这种写法可以用在线程的最外层, 以避免程序崩溃. + + 如果你使用这种写法, Python 将非常宽容. ``except:`` 真的会捕获任何错误, 包括拼写错误的符号名、 ``sys.exit()`` 调用、 ``Ctrl+C`` 中断、单元测试错误和各种你不想捕获的错误. + + #. 最小化 ``try/except`` 代码块中的代码量. ``try`` 的范围越大, 就越容易把你没想到的那些能抛出异常的代码囊括在内. 这样的话, ``try/except`` 代码块就掩盖了真正的错误. + #. 用 ``finally`` 表示无论异常与否都应执行的代码. 这种写法常用于清理资源, 例如关闭文件. 全局变量 -------------------- .. tip:: - 避免全局变量 + 避免全局变量. 定义: - 定义在模块级的变量. + 在程序运行时可以发生变化的模块级变量和类属性. 优点: 偶尔有用. 缺点: - 导入时可能改变模块行为, 因为导入模块时会对模块级变量赋值. + #. 破坏封装: 这种设计会阻碍一些有用的目标. 例如, 如果用全局变量来管理数据库连接, 那就难以同时连接两个数据库 (比如为了在数据迁移时比较差异). 全局注册表也有类似的问题. + #. 导入模块时可能改变模块的行为, 因为首次导入模块时会对全局变量赋值. 结论: 避免使用全局变量. - 鼓励使用模块级的常量,例如 ``MAX_HOLY_HANDGRENADE_COUNT = 3``.注意常量命名必须全部大写,用 ``_`` 分隔.具体参见 `命名规则 `_ - 若必须要使用全局变量,应在模块内声明全局变量,并在名称前 ``_`` 使之成为模块内部变量.外部访问必须通过模块级的公共函数.具体参见 `命名规则 <>`_ + + 在特殊情况下需要用到全局变量时, 应将全局变量声明为模块级变量或者类属性, 并在名称前加 `_` 以示为内部状态. 如需从外部访问全局变量, 必须通过公有函数或类方法实现. 详见 `命名规则 `_ 章节. 请用注释或文档链接解释这些全局变量的设计思想. + 我们允许并鼓励使用模块级常量,例如 ``_MAX_HOLY_HANDGRENADE_COUNT = 3`` 表示内部常量, ``SIR_LANCELOTS_FAVORITE_COLOR = "blue"`` 表示公开API的常量. 注意常量名必须全部大写, 用下划线分隔单词. 详见 `命名规则 `_ 章节. -嵌套/局部/内部类或函数 +嵌套/局部/内部类和函数 ------------------------ .. tip:: - 使用内部类或者嵌套函数可以用来覆盖某些局部变量. + 可以用局部类和局部函数来捕获局部变量. 可以用内部类. 定义: - 类可以定义在方法, 函数或者类中. 函数可以定义在方法或函数中. 封闭区间中定义的变量对嵌套函数是只读的. (译者注:即内嵌函数可以读外部函数中定义的变量,但是无法改写,除非使用 `nonlocal`) + 可以在方法、函数和类中定义内部类. 可以在方法和函数中定义嵌套函数. 嵌套函数可以只读访问外层作用域中的变量. (译者注:即内嵌函数可以读外部函数中定义的变量,但是无法改写,除非使用 `nonlocal`) 优点: - 允许定义仅用于有效范围的工具类和函数.在装饰器中比较常用. + 方便定义作用域有限的工具类和函数. 便于实现 `抽象数据类型 `_. 常用于实现装饰器. 缺点: - 嵌套类或局部类的实例不能序列化(pickled). 内嵌的函数和类无法直接测试.同时内嵌函数和类会使外部函数的可读性变差. + 无法直接测试嵌套的函数和类. 嵌套函数和嵌套类会让外层函数的代码膨胀, 可读性变差. 结论: - 使用内部类或者内嵌函数可以忽视一些警告.但是应该避免使用内嵌函数或类,除非是想覆盖某些值.若想对模块的用户隐藏某个函数,不要采用嵌套它来隐藏,应该在需要被隐藏的方法的模块级名称加 ``_`` 前缀,这样它依然是可以被测试的. + 可以谨慎使用. 尽量避免使用嵌套函数和嵌套类, 除非需要捕获 ``self`` 和 ``cls`` 以外的局部变量. 不要仅仅为了隐藏一个函数而使用嵌套函数. 应将需要隐藏的函数定义在模块级别, 并给名称加上 ``_`` 前缀, 以便在测试代码中调用此函数. -推导式&生成式 +推导式和生成式 -------------------------------- .. tip:: - 可以在简单情况下使用 + 适用于简单情况. 定义: - 列表,字典和集合的推导&生成式提供了一种简洁高效的方式来创建容器和迭代器, 而不必借助map(), filter(), 或者lambda.(译者注: 元组是没有推导式的, ``()`` 内加类似推导式的句式返回的是个生成器) + 列表、字典和集合的推导式和生成式可以用于简洁高效地创建容器和迭代器, 而无需借助循环、 ``map()``、 ``filter()``, 或者 ``lambda`` . (译者注: 元组是没有推导式的, ``()`` 内加类似推导式的句式返回的是个生成器) 优点: - 简单的列表推导可以比其它的列表创建方法更加清晰简单. 生成器表达式可以十分高效, 因为它们避免了创建整个列表. + 相较于其它创建字段、列表和集合的方法, 简单的列表推导式更加清晰和简洁. 生成器表达式十分高效, 因为无需创建整个列表. 缺点: - 复杂的列表推导或者生成器表达式可能难以阅读. + 复杂的列表推导式和生成式难以理解. 结论: - 适用于简单情况. 每个部分应该单独置于一行: 映射表达式, for语句, 过滤器表达式. 禁止多重for语句或过滤器表达式. 复杂情况下还是使用循环. + 可以用于简单情况. 以下每个部分不应超过一行: 映射表达式、for语句和过滤表达式. 禁止多重for语句和多层过滤. 情况复杂时, 应该用循环. - Yes: + 正确: .. code-block:: python result = [mapping_expr for value in iterable if filter_expr] result = [{'key': value} for value in iterable - if a_long_filter_expression(value)] + if a_long_filter_expression(value)] result = [complicated_transform(x) - for x in iterable if predicate(x)] + for x in iterable if predicate(x)] descriptive_name = [ transform({'key': key, 'value': value}, color='black') @@ -290,53 +311,55 @@ Lint eat(jelly_bean for jelly_bean in jelly_beans if jelly_bean.color == 'black') - No: + 错误: .. code-block:: python - - result = [(x, y) for x in range(10) for y in range(5) if x * y > 10] - return ((x, y, z) - for x in xrange(5) - for y in xrange(5) - if x != y - for z in xrange(5) - if y != z) + 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 xrange(5) + for y in xrange(5) + if x != y + for z in xrange(5) + if y != z) 默认迭代器和操作符 -------------------- .. tip:: - 如果类型支持, 就使用默认迭代器和操作符. 比如列表, 字典及文件等. + 只要可行, 就用列表、字典和文件等类型的默认迭代器和操作符. 定义: - 容器类型, 像字典和列表, 定义了默认的迭代器和关系测试操作符(in和not in) + 字典和列表等容器类型具有默认的迭代器和关系运算符 ( ``in`` 和 ``not in`` ). 优点: - 默认操作符和迭代器简单高效, 它们直接表达了操作, 没有额外的方法调用. 使用默认操作符的函数是通用的. 它可以用于支持该操作的任何类型. + 默认迭代器和操作符简单高效. 这种写法可以直白地表达运算, 无需调用额外的函数. 使用默认操作符的函数是泛型函数, 可以用于任何支持该操作符的类型. 缺点: - 你没法通过阅读方法名来区分对象的类型(例如, has_key()意味着字典). 不过这也是优点. + 你不能通过方法名来辨别对象的类型 (除非变量有类型注解). 不过这也是优点. 结论: - 如果类型支持, 就使用默认迭代器和操作符, 例如列表, 字典和文件. 内建类型也定义了迭代器方法. 优先考虑这些方法, 而不是那些返回列表的方法. 当然,这样遍历容器时,你将不能修改容器. 除非必要,否则不要使用诸如 `dict.iter*()` 这类python2的特定迭代方法. + 只要是支持的类型 (例如列表、字典和文件), 就使用默认迭代器和操作符. 内置类型也定义了一些返回迭代器的方法. 优先使用返回迭代器的方法, 而非返回列表的方法, 不过注意使用迭代器时不能修改容器. - Yes: + 正确: .. code-block:: python for key in adict: ... - if key not in adict: ... if obj in alist: ... for line in afile: ... - for k, v in dict.iteritems(): ... + for k, v in adict.items(): ... - No: + 错误: .. code-block:: python for key in adict.keys(): ... - if not adict.has_key(key): ... for line in afile.readlines(): ... 生成器 @@ -346,116 +369,131 @@ Lint 按需使用生成器. 定义: - 所谓生成器函数, 就是每当它执行一次生成(yield)语句, 它就返回一个迭代器, 这个迭代器生成一个值. 生成值后, 生成器函数的运行状态将被挂起, 直到下一次生成. + 生成器函数会返回一个迭代器. 每当函数执行 ``yield`` 语句时, 迭代器就生成一个值. 随后, 生成器的运行状态将暂停, 直到需要下一个值的时候. 优点: - 简化代码, 因为每次调用时, 局部变量和控制流的状态都会被保存. 比起一次创建一系列值的函数, 生成器使用的内存更少. + 代码简单, 因为生成器可以保存局部变量和控制流. 相较于直接创建整个列表的函数, 生成器使用的内存更少. 缺点: - 没有. + 必须等到生成结束或者生成器本身被内存回收的时候, 生成器的局部变量才能被内存回收. 结论: - 鼓励使用. 注意在生成器函数的文档字符串中使用"Yields:"而不是"Returns:". + 可以使用. 生成器的文档字符串中应使用"Yields:"而不是"Returns:". (译者注: 参看 :ref:`注释` ) + + 如果生成器占用了大量资源, 一定要强制清理资源. + + 一种清理资源的好方法是用上下文管理器包裹生成器 `PEP-0533 `_. Lambda函数 -------------------- .. tip:: - 适用于单行函数 + 适用于单行函数. 建议用生成式替代 ``map()/filter()`` 与 ``lambda`` 的组合. 定义: - 与语句相反, lambda在一个表达式中定义匿名函数. 常用于为 ``map()`` 和 ``filter()`` 之类的高阶函数定义回调函数或者操作符. + lambda 定义匿名函数, 不像语句那样定义具名函数. 优点: 方便. 缺点: - 比本地函数更难阅读和调试. 没有函数名意味着堆栈跟踪更难理解. 由于lambda函数通常只包含一个表达式, 因此其表达能力有限. + 比局部函数更难理解和调试. 缺失函数名会导致调用栈晦涩难懂. 由于 lambda 函数只能包含一个表达式, 因此其表达能力有限. 结论: - 适用于单行函数. 如果代码超过60-80个字符, 最好还是定义成常规(嵌套)函数. + 适用于单行函数. 如果函数体超过60-80个字符, 最好还是定义为常规的嵌套函数. - 对于常见的操作符,例如乘法操作符,使用 ``operator`` 模块中的函数以代替lambda函数. 例如, 推荐使用 ``operator.mul`` , 而不是 ``lambda x, y: x * y`` . + 对于乘法等常见操作, 应该用 ``operator`` 模块中的函数代替lambda函数. 例如, 推荐用 ``operator.mul`` 代替 ``lambda x, y: x * y`` . 条件表达式 -------------------- .. tip:: - 适用于单行函数 + 适用于简单情况. 定义: - 条件表达式(又名三元运算符)是对于if语句的一种更为简短的句法规则. 例如: ``x = 1 if cond else 2`` . + 条件表达式(又名三元运算符)是if语句的缩略版. 例如: ``x = 1 if cond else 2`` . 优点: - 比if语句更加简短和方便. + 比if语句更简短, 更方便. 缺点: - 比if语句难于阅读. 如果表达式很长, 难于定位条件. + 有时比if语句更难理解. 如果表达式很长,就难以一眼望到条件. 结论: - 适用于单行函数. 写法上推荐真实表达式,if表达式,else表达式每个独占一行.在其他情况下,推荐使用完整的if语句. + 适用于简单情况. 以下每部分均不得长于一行: 真值分支, if 部分和 else 部分. 情况复杂时应使用完整的if语句. + + 正确: .. code-block:: python one_line = 'yes' if predicate(value) else 'no' slightly_split = ('yes' if predicate(value) - else 'no, nein, nyet') + 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') + 错误: + .. code-block:: python bad_line_breaking = ('yes' if predicate(value) else - 'no') + 'no') # 换行位置错误 portion_too_long = ('yes' if some_long_module.some_long_predicate_function( really_long_variable_name) - else 'no, false, negative, nay') + else 'no, false, negative, nay') # 过长 默认参数值 -------------------- .. tip:: - 适用于大部分情况. + 大部分情况下允许. 定义: - 你可以在函数参数列表的最后指定变量的值, 例如, ``def foo(a, b = 0):`` . 如果调用foo时只带一个参数, 则b被设为0. 如果带两个参数, 则b的值等于第二个参数. + 你可以为参数列表的最后几个参数赋予默认值, 例如, ``def foo(a, b = 0):`` . 如果调用foo时只带一个参数, 则b为0. 如果调用时带两个参数, 则b的值等于第二个参数. 优点: - 你经常会碰到一些使用大量默认值的函数, 但偶尔(比较少见)你想要覆盖这些默认值. 默认参数值提供了一种简单的方法来完成这件事, 你不需要为这些罕见的例外定义大量函数. 同时, Python也不支持重载方法和函数, 默认参数是一种"仿造"重载行为的简单方式. + 很多时候, 你需要一个拥有大量默认值的函数, 并且偶尔需要覆盖这些默认值. 通过默认参数值可以轻松实现这种功能, 不需要为了覆盖默认值而编写大量额外的函数. 同时, Python不支持重载方法和函数, 而默认参数的写法可以轻松"仿造"重载行为. 缺点: - 默认参数只在模块加载时求值一次. 如果参数是列表或字典之类的可变类型, 这可能会导致问题. 如果函数修改了对象(例如向列表追加项), 默认值就被修改了. + 默认参数在模块被导入时求值且只计算一次. 如果值是列表和字典等可变类型, 就可能引发问题. 如果函数修改了这个值(例如往列表内添加元素), 默认值就变化了. 结论: - 鼓励使用, 不过有如下注意事项: + 可以使用, 不过有如下注意事项: - 不要在函数或方法定义中使用可变对象作为默认值. + 函数和方法的默认值不能是可变对象. + + 正确: .. code-block:: python - 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 + def foo(a, b=None): + if b is None: + b = [] + def foo(a, b: Optional[Sequence] = None): + if b is None: + b = [] + def foo(a, b: Sequence = ()): # 允许空元组,因为元组是不可变的 - .. code-block:: python + 错误: - No: def foo(a, b=[]): + .. code-block:: python + + from absl import flags + _FOO = flags.DEFINE_string(...) + + def foo(a, b=[]): ... - No: def foo(a, b=time.time()): # The time the module was loaded??? + def foo(a, b=time.time()): # 确定要用模块的导入时间吗??? ... - No: def foo(a, b=FLAGS.my_thing): # sys.argv has not yet been parsed... + def foo(a, b=_FOO.value): # 此时还没有解析 sys.argv... ... - No: def foo(a, b: Mapping = {}): # Could still get passed to unchecked code + def foo(a, b: Mapping = {}): # 可能会赋值给未经过静态检查的代码 ... @@ -465,103 +503,59 @@ Lambda函数 (译者注:参照fluent python.这里将 "property" 译为"特性",而 "attribute" 译为属性. python中数据的属性和处理数据的方法统称属性"(arrtibute)", 而在不改变类接口的前提下用来修改数据属性的存取方法我们称为"特性(property)".) .. tip:: - 访问和设置数据成员时, 你通常会使用简单, 轻量级的访问和设置函数.建议使用特性(properties)来代替它们. + 可以用特性来读取、写入涉及简单计算、逻辑的属性. 特性的实现必须和属性一样满足这些通用要求: 轻量、直白、明确. 定义: - 一种用于包装方法调用的方式. 当运算量不大, 它是获取和设置属性(attribute)的标准方式. + 把读取、写入属性的函数包装为常规属性操作的写法. 优点: - 通过消除简单的属性(attribute)访问时显式的get和set方法调用, 可读性提高了. 允许懒惰的计算. 用Pythonic的方式来维护类的接口. 就性能而言, 当直接访问变量是合理的, 添加访问方法就显得琐碎而无意义. 使用特性(properties)可以绕过这个问题. 将来也可以在不破坏接口的情况下将访问方法加上. + #. 可以直接实现属性的访问、赋值接口, 而不必添加访问函数 (getter) 和变异函数 (setter). + #. 可以让属性变为只读. + #. 可以实现惰性求值. + #. 类的内部实现发生变化时, 可以用这种方法让用户看到的公开接口保持不变. 缺点: - 特性(properties)是在get和set方法声明后指定, 这需要使用者在接下来的代码中注意: set和get是用于特性(properties)的(除了用 ``@property`` 装饰器创建的只读属性). 必须继承自object类. 可能隐藏比如操作符重载之类的副作用. 继承时可能会让人困惑. - (译者注:这里没有修改原始翻译,其实就是 @property 装饰器是不会被继承的) + #. 可能掩盖副作用, 类似运算符重载. + #. 子类继承时可能产生困惑. 结论: - 你通常习惯于使用访问或设置方法来访问或设置数据, 它们简单而轻量. 不过我们建议你在新的代码中使用属性. 只读属性应该用 ``@property`` `装饰器 `_ 来创建. - - 如果子类没有覆盖属性, 那么属性的继承可能看上去不明显. 因此使用者必须确保访问方法间接被调用, 以保证子类中的重载方法被属性调用(使用模板方法设计模式). + 允许使用特性, 但是, 和运算符重载一样, 只能在必要时使用, 并且要模仿常规属性的存取特点. 若无法满足要求, 请参考访问函数和变异函数的规则. - .. code-block:: python + 举个例子, 一个特性不能仅仅用于获取和设置一个内部属性: 因为不涉及计算, 没有必要用特性 (应该把该属性设为公有). 而用特性来限制属性的访问或者计算 **简单** 的衍生值则是正确的: 这种逻辑简单明了. - Yes: - import math + 应该用 ``@property`` `装饰器 `_ 来创建特性. 自行实现的特性装饰器属于威力过大的功能. - class Square: - """A square with two properties: a writable area and a read-only perimeter. - - To use: - >>> sq = Square(3) - >>> sq.area - 9 - >>> sq.perimeter - 12 - >>> sq.area = 16 - >>> sq.side - 4 - >>> sq.perimeter - 16 - """ - - def __init__(self, side): - self.side = side - - @property - def area(self): - """Area of the square.""" - return self._get_area() - - @area.setter - def area(self, area): - return self._set_area(area) - - def _get_area(self): - """Indirect accessor to calculate the 'area' property.""" - return self.side ** 2 - - def _set_area(self, area): - """Indirect setter to set the 'area' property.""" - self.side = math.sqrt(area) - - @property - def perimeter(self): - return self.side * 4 - - (译者注: 老实说, 我觉得这段示例代码很不恰当, 有必要这么蛋疼吗?) + 特性的继承机制难以理解. 不要用特性实现子类能覆盖或扩展的计算功能. True/False的求值 -------------------- .. tip:: - 尽可能使用隐式false + 尽可能使用"隐式"假值. 定义: - Python在布尔上下文中会将某些值求值为false. 按简单的直觉来讲, 就是所有的"空"值都被认为是false. 因此0, None, [], {}, "" 都被认为是false. + Python在计算布尔值时会把一些值视为 ``False``. 简单来说, 所有的"空"值都是假值. 因此, ``0, None, [], {}, ""`` 作为布尔值使用时相当于 ``False``. 优点: - 使用Python布尔值的条件语句更易读也更不易犯错. 大部分情况下, 也更快. + Python布尔值可以让条件语句更易懂, 减少失误. 多数时候运行速度也更快. 缺点: 对C/C++开发人员来说, 可能看起来有点怪. 结论: - 尽可能使用隐式的false, 例如: 使用 ``if foo:`` 而不是 ``if foo != []:`` . 不过还是有一些注意事项需要你铭记在心: + 尽可能使用"隐式"假值, 例如: 使用 ``if foo:`` 而非 ``if foo != []:`` . 不过还是有一些注意事项需要你铭记在心: - #. 对于 ``None`` 等单例对象测试时,使用 ``is`` 或者 ``is not``.当你要测试一个默认值是None的变量或参数是否被设为其它值. 这个值在布尔语义下可能是false! - (译者注: ``is`` 比较的是对象的id(), 这个函数返回的通常是对象的内存地址,考虑到CPython的对象重用机制,可能会出现生命周不重叠的两个对象会有相同的id) - #. 永远不要用==将一个布尔量与false相比较. 使用 ``if not x:`` 代替. 如果你需要区分false和None, 你应该用像 ``if not x and x is not None:`` 这样的语句. - #. 对于序列(字符串, 列表, 元组), 要注意空序列是false. 因此 ``if not seq:`` 或者 ``if seq:`` 比 ``if len(seq):`` 或 ``if not len(seq):`` 要更好. - #. 处理整数时, 使用隐式false可能会得不偿失(即不小心将None当做0来处理). 你可以将一个已知是整型(且不是len()的返回结果)的值与0比较. + #. 一定要用 ``if foo is None:`` (或者 ``is not None``) 来检测 ``None`` 值. 例如, 如果你要检查某个默认值为 ``None`` 的参数有没有被调用者覆盖, 覆盖的值在布尔语义下可能也是假值! + #. 永远不要用 ``==`` 比较一个布尔值是否等于 ``False``. 应该用 ``if not x:`` 代替. 如果你需要区分 ``False`` 和 ``None``, 你应该用复合表达式, 例如 ``if not x and x is not None:``. + #. 多利用空序列(字符串, 列表, 元组)是假值的特性. 因此 ``if not seq:`` 比 ``if len(seq):`` 更好, ``if not seq:`` 比 ``if not len(seq):`` 更好. + #. 处理整数时, 使用隐式false可能会得不偿失(例如不小心将 ``None`` 当做0来处理). 你可以显式比较整型值与0的关系 (``len()`` 的返回值例外). - Yes: + 正确: .. code-block:: python if not users: - print('no users') - - if foo == 0: - self.handle_zero() + print('无用户') if i % 10 == 0: self.handle_multiple_of_ten() @@ -570,15 +564,12 @@ True/False的求值 if x is None: x = [] - No: + 错误: .. code-block:: python if len(users) == 0: - print 'no users' - - if foo is not None and not foo: - self.handle_zero() + print '无用户' if not i % 10: self.handle_multiple_of_ten() @@ -586,191 +577,160 @@ True/False的求值 def f(x=None): x = x or [] - #. 注意'0'(字符串)会被当做true. - -过时的语言特性 --------------------- - -.. tip:: - 尽可能使用字符串方法取代字符串模块. 使用函数调用语法取代apply(). 使用列表推导, for循环取代filter(), map()以及reduce(). - -定义: - 当前版本的Python提供了大家通常更喜欢的替代品. - -结论: - 我们不使用不支持这些特性的Python版本, 所以没理由不用新的方式. + #. 注意, '0'(字符串, 不是整数)作为布尔值时等于 ``True``. + #. 注意, Numpy 数组转换为布尔值时可能抛出异常. 因此建议用 `.size` 属性检查 ``np.array`` 是否为空 (例如 ``if not users.size``). - .. code-block:: python - - 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) - - .. code-block:: python - - No: words = string.split(foo, ':') - - map(lambda x: x[1], filter(lambda x: x[2] == 5, my_list)) - - apply(fn, args, kwargs) - -词法作用域(Lexical Scoping) +词法作用域(Lexical Scoping, 又名静态作用域) ----------------------------- .. tip:: - 推荐使用 + 可以使用. 定义: - 嵌套的Python函数可以引用外层函数中定义的变量, 但是不能够对它们赋值. 变量绑定的解析是使用词法作用域, 也就是基于静态的程序文本. 对一个块中的某个名称的任何赋值都会导致Python将对该名称的全部引用当做局部变量, 甚至是赋值前的处理. 如果碰到global声明, 该名称就会被视作全局变量. + 嵌套的Python函数可以引用外层函数中定义的变量, 但是不能对这些变量赋值. 变量的绑定分析基于词法作用域, 也就是基于静态的程序文本. 任何在代码块内给标识符赋值的操作, 都会让Python将该标识符的所有引用变成局部变量, 即使读取语句写在赋值语句之前. 如果有全局声明, 该标识符会被视为全局变量. 一个使用这个特性的例子: .. code-block:: python - def get_adder(summand1): - """Returns a function that adds numbers to a given number.""" - def adder(summand2): + def get_adder(summand1: float) -> Callable[[float], float]: + """返回一个函数,该函数会给一个数字加上指定的值.""" + def adder(summand2: float) -> float: return summand1 + summand2 return adder - (译者注: 这个例子有点诡异, 你应该这样使用这个函数: ``sum = get_adder(summand1)(summand2)`` ) + (译者注: 这个函数的用法大概是: ``fn = get_adder(1.2); sum = fn(3.4)``, 结果是 ``sum == 4.6``.) 优点: - 通常可以带来更加清晰, 优雅的代码. 尤其会让有经验的Lisp和Scheme(还有Haskell, ML等)程序员感到欣慰. + 通常会产生更清晰、更优雅的代码. 尤其是让熟练使用Lisp和Scheme(还有Haskell, ML等)的程序员感到舒适. 缺点: - 可能导致让人迷惑的bug. 例如下面这个依据 `PEP-0227 `_ 的例子: + 可能引发让人困惑的bug, 例如下面这个依据 `PEP-0227 `_ 改编的例子: .. code-block:: python i = 4 - def foo(x): + def foo(x: Iterable[int]): def bar(): - print i, + print(i, end='') # ... - # A bunch of code here + # 很多其他代码 # ... - for i in x: # Ah, i *is* local to Foo, so this is what Bar sees - print i, + for i in x: # 啊哈, i 是 Foo 的局部变量, 所以 bar 得到的是这个变量 + print(i, end='') bar() - 因此 ``foo([1, 2, 3])`` 会打印 ``1 2 3 3`` , 不是 ``1 2 3 4`` . + 因此 ``foo([1, 2, 3])`` 会输出 ``1 2 3 3`` , 而非 ``1 2 3 4`` . - (译者注: x是一个列表, for循环其实是将x中的值依次赋给i.这样对i的赋值就隐式的发生了, 整个foo函数体中的i都会被当做局部变量, 包括bar()中的那个. 这一点与C++之类的静态语言还是有很大差别的.) + (译者注: x是一个列表, for循环其实是将x中的值依次赋给i.这样对i的赋值就隐式的发生了, 整个foo函数体中的i都会被当做局部变量, 包括bar()中的那个. 这一点与C++之类的语言还是有很大差别的.) 结论: - 鼓励使用. + 可以使用. 函数与方法装饰器 -------------------- .. tip:: - 如果好处很显然, 就明智而谨慎的使用装饰器,避免使用 ``staticmethod``以及谨慎使用``classmethod``. + 仅在有显著优势时, 审慎地使用装饰器. 避免使用 ``staticmethod``. 减少使用 ``classmethod``. 定义: - `用于函数及方法的装饰器 `_ (也就是@标记). 最常见的装饰器是@classmethod 和@staticmethod, 用于将常规函数转换成类方法或静态方法. 不过, 装饰器语法也允许用户自定义装饰器. 特别地, 对于某个函数 ``my_decorator`` , 下面的两段代码是等效的: + `装饰器(也就是@标记)作用在函数和方法上 `_. 常见的装饰器是 ``@property``, 用于把方法转化为动态求值的属性. 不过, 也可以用装饰器语法自行定义装饰器. 具体地说, 若有一个函数 ``my_decorator`` , 下面两段代码是等效的: .. code-block:: python class C(object): @my_decorator def method(self): - # method body ... + # 函数体 ... .. code-block:: python class C(object): def method(self): - # method body ... + # 函数体 ... method = my_decorator(method) 优点: - 优雅的在函数上指定一些转换. 该转换可能减少一些重复代码, 保持已有函数不变(enforce invariants), 等. + 优雅地实现函数的变换; 这种变换可用于减少重复的代码, 或帮助检查不变式 (invariant). 缺点: - 装饰器可以在函数的参数或返回值上执行任何操作, 这可能导致让人惊异的隐藏行为. 而且, 装饰器在导入时执行. 从装饰器代码中捕获错误并处理是很困难的. + 装饰器可以在函数的参数和返回值上执行任何操作, 这可能产生意外且隐蔽的效果. 而且, 装饰是在定义对象时执行. 模块级对象(类、模块级函数)的装饰器在导入模块时执行. 当装饰器代码出错时, 很难恢复正常控制流. 结论: - 如果好处很显然, 就明智而谨慎的使用装饰器. 装饰器应该遵守和函数一样的导入和命名规则. 装饰器的python文档应该清晰的说明该函数是一个装饰器. 请为装饰器编写单元测试. + 仅在有显著优势时, 审慎地使用装饰器. 装饰器的导入和命名规则与函数相同. 装饰器的pydoc注释应清楚地说明该函数是装饰器. 请为装饰器编写单元测试. - 避免装饰器自身对外界的依赖(即不要依赖于文件, socket, 数据库连接等), 因为装饰器运行时这些资源可能不可用(由 ``pydoc`` 或其它工具导入). 应该保证一个用有效参数调用的装饰器在所有情况下都是成功的. + 避免装饰器自身对外界的依赖(即不要依赖于文件, 套接字, 数据库连接等), 因为执行装饰器时(即导入模块时. ``pydoc`` 和其他工具也会导入你的模块) 可能无法连接到这些环境. 只要装饰器的调用参数正确, 装饰器应该 (尽最大努力) 保证运行成功. - 装饰器是一种特殊形式的"顶级代码". 参考后面关于 :ref:`Main
` 的话题. + 装饰器是一种特殊形式的"顶级代码". 参见关于 :ref:`Main
` 的章节. - 除非是为了将方法和现有的API集成,否则不要使用 ``staticmethod`` .多数情况下,将方法封装成模块级的函数可以达到同样的效果. + 不得使用 ``staticmethod``, 除非为了兼容老代码库的 API 不得已而为之. 应该把静态方法改写为模块级函数. - 谨慎使用 ``classmethod`` .通常只在定义备选构造函数,或者写用于修改诸如进程级缓存等必要的全局状态的特定类方法才用。 + 仅在以下情况可以使用 ``classmethod``: 实现具名构造函数(named constructor); 在类方法中修改必要的全局状态 (例如进程内共享的缓存等)。 线程 -------------------- .. tip:: - 不要依赖内建类型的原子性. + 不要依赖内置类型的原子性. -虽然Python的内建类型例如字典看上去拥有原子操作, 但是在某些情形下它们仍然不是原子的(即: 如果__hash__或__eq__被实现为Python方法)且它们的原子性是靠不住的. 你也不能指望原子变量赋值(因为这个反过来依赖字典). +虽然Python的内置类型表面上有原子性, 但是在特定情形下可能打破原子性(例如用Python实现 ``__hash__`` 或 ``__eq__`` 的情况下). 因此它们的原子性不可靠. 你也不能臆测赋值是原子性的(因为赋值的原子性依赖于字典的原子性). -优先使用Queue模块的 ``Queue`` 数据类型作为线程间的数据通信方式. 另外, 使用threading模块及其锁原语(locking primitives). 了解条件变量的合适使用方式, 这样你就可以使用 ``threading.Condition`` 来取代低级别的锁了. +选择线程间的数据传递方式时, 应优先考虑 ``queue`` 模块的 ``Queue`` 数据类型. 如果不适用, 则使用 ``threading`` 模块及其提供的锁原语(locking primitives). 如果可行, 应该用条件变量和 ``threading.Condition`` 替代低级的锁. 威力过大的特性 -------------------- .. tip:: - 避免使用这些特性 + 避开这些特性. 定义: - Python是一种异常灵活的语言, 它为你提供了很多花哨的特性, 诸如元类(metaclasses), 字节码访问, 任意编译(on-the-fly compilation), 动态继承, 对象父类重定义(object reparenting), 导入黑客(import hacks), 反射, 系统内修改(modification of system internals), 等等. + Python是一种异常灵活的语言, 有大量花哨的特性, 诸如自定义元类(metaclasses), 读取字节码(bytecode), 及时编译(on-the-fly compilation), 动态继承, 对象父类重定义(object reparenting), 导入(import)技巧, 反射(例如 ``getattr()``), 系统内部状态的修改, ``__del__`` 实现的自定义清理等等. 优点: - 强大的语言特性, 能让你的代码更紧凑. + 强大的语言特性让代码紧凑. 缺点: - 使用这些很"酷"的特性十分诱人, 但不是绝对必要. 使用奇技淫巧的代码将更加难以阅读和调试. 开始可能还好(对原作者而言), 但当你回顾代码, 它们可能会比那些稍长一点但是很直接的代码更加难以理解. + 这些很"酷"的特性十分诱人, 但多数情况下没必要使用. 包含奇技淫巧的代码难以阅读、理解和调试. 一开始可能还好(对原作者而言), 但以后回顾代码时, 这种代码通常比那些长而直白的代码更加深奥. 结论: - 在你的代码中避免这些特性. + 避开这些特性. - 当然,利用了这些特性的来编写的一些标准库是值得去使用的,比如 ``abc.ABCMeta``, ``collection.namedtuple``, ``dataclasses`` , ``enum``等. + 可以使用那些在内部利用了这些特性的标准模块和类, 比如 ``abc.ABCMeta``, ``dataclasses`` 和 ``enum``. -现代python: python3 和from __future__ imports +现代python: from __future__ imports -------------------- .. tip:: - 尽量使用 python3, 即使使用非 python3 写的代码.也应该尽量兼容. + 可以通过导入 ``__future__`` 包, 在较老的运行时上启用新语法, 并且只在特定文件上生效. 定义: - python3 是 python 的一个重大变化,虽然已有大量代码是 python2.7 写的,但是通过一些简单的调整,就可以使之在 python3 下运行. + 通过使用 ``from __future__ import`` 并启用现代的语法, 可以提前使用未来的 Python 特性. 优点: - 只要确定好项目的所有依赖,那么用 python3 写代码可以更加清晰和方便运行. + 实践表明, 该功能可以让版本升级过程更稳定, 因为可以逐步修改各个文件, 并用这样的兼容性声明来防止退化 (regression). 现代的代码便于维护, 因为不容易积累那些阻碍运行时升级的技术债. 缺点: - 导入一些看上去实际用不到的模块到代码里显得有些奇葩. + 此类代码无法在过老的运行时上运行, 过老的版本可能没有实现所需的 ``future`` 功能. 这个问题在那些需要支持大量不同环境的项目中尤为明显. 结论: **from __future__ imports** - 鼓励使用 ``from __future__ import`` 语句,所有的新代码都应该包含以下内容,并尽可能的与之兼容: + 鼓励使用 ``from __future__ import`` 语句. 这样, 你的源代码从今天起就能使用更现代的 Python 语法. 当你不再需要支持老版本时, 请自行删除这些导入语句. + + 如果你的代码要支持 3.5 版本, 而不是常规的 ``>=3.7``, 请导入: .. code-block:: python - from __future__ import absolute_import - from __future__ import division - from __future__ import print_function + from __future__ import generator_stop - 以上导入的详情参见 `absolute imports `_ , `division behavior `_, `print function `_ . - 除非代码是只在python3下运行,否则不要删除以上导入.最好在所有文件里都保留这样的导入,这样若有人用到了这些方法时,编辑时不会忘记导入. - 还有其他的一些来自 ``from __future__`` 的语句.请在你认为合适的地方使用它们.本文没有推荐 ``unicode_literals`` ,因为我们认为它不是很棒的改进,它在 python2.7 中大量引入例隐式的默认编码转换.大多数情况下还是推荐显式的使用 ``b`` 和 ``u`` 以及 unicode字符串来显式的指示编码转换. + 详情参见 `Python future 语句 `_ 的文档. + + 除非你确定代码的运行环境已经足够现代, 否则不要删除 future 语句. 即使你用不到 future 语句, 也要保留它们, 以免其他编辑者不小心对旧的特性产生依赖. - **six,future,past** - - 当项目需要同时支持 python2 和 python3 时,请根据需要使用 `six `_ , `future `_ , `past `_ . 这些库可以使代码更加清晰和简单. + 在你认为恰当的时候, 可以使用其他来自 ``from __future__`` 的语句. 代码类型注释 @@ -778,10 +738,10 @@ True/False的求值 .. tip:: 你可以根据 `PEP-484 `_ 来对 python3 代码进行注释,并使用诸如 `pytype `_ 之类的类型检查工具来检查代码. - 类型注释既可以写在源码,也可以写在 `pyi `_ 中.推荐尽量写在源码里,对于第三方扩展包,可以写在pyi文件里. + 类型注释既可以写在源码里,也可以写在 `pyi `_ 中. 推荐尽量写在源码里. 对于第三方代码和扩展包, 请使用 pyi 文件里 定义: - 用于函数参数和返回值的类型注释: + 用在函数参数和返回值上: .. code-block:: python @@ -793,19 +753,13 @@ True/False的求值 a: SomeType = some_func() - 在必须支持老版本 python 运行的代码中则可以这样注释: - - .. code-block:: python - - a = some_func() #type: SomeType - 优点: - 可以提高代码可读性和可维护性.同时一些类型检查器可以帮您提早发现一些运行时错误,并降低您使用大威力特性的必要. + 可以提高代码可读性和可维护性. 类型检查器可以把运行时错误变成编译错误, 并阻止你使用威力过大的特性. 缺点: - 必须时常更新类型声明.过时的类型声明可能会误导您.使用类型检查器会抑制您使用大威力特性. + 必须时常更新类型声明. 正确的代码也可能有误报. 无法使用威力大的特性. 结论: - 强烈推荐您在更新代码时使用 python 类型分析.在添加或修改公共API时使用类型注释,在最终构建整个项目前使用 pytype 来进行检查.由于静态分析对于 python 来说还不够成熟,因此可能会出现一些副作用(例如错误推断的类型)可能会阻碍项目的部署.在这种情况下,建议作者添加一个 TODO 注释或者链接,来描述当前构建文件或是代码本身中使用类型注释导致的问题. + 强烈推荐你在更新代码时启用 python 类型分析. 在添加或修改公开API时, 请添加类型注释, 并在构建系统(build system)中启用 pytype. 由于python静态分析是新功能, 因此一些意外的副作用(例如类型推导错误)可能会阻碍你的项目采纳这一功能. 在这种情况下, 建议作者在 BUILD 文件或者代码中添加一个 TODO 注释或者链接, 描述那些阻碍采用类型注释的问题. (译者注: 代码类型注释在帮助IDE或是vim等进行补全倒是很有效)