为了账号安全,请及时绑定邮箱和手机立即绑定
慕课专栏

目录

索引目录

Python 核心技术精讲

限时优惠 ¥ 58.00

原价 ¥ 78.00

07月23日后恢复原价

限时优惠
立即订阅
03 无规矩不成方圆:走进 PEP 8,掌握 Python 编码规范(上)
更新时间:2020-06-22 22:33:10
学习这件事不在乎有没有人教你,最重要的是在于你自己有没有觉悟和恒心。

—— 法布尔

引言

什么是编码规范?编码规范是指在编写代码时,应遵循的一些通用的编写方式或编写风格。在语言层面上,编码规范不同于语法,并非是强制性的,但往往在工程能力较为成熟的公司或组织中,统一的编码规范通常是被强制执行的。本小节则会从编码规范的作用、PEP 与 PEP 8 及 Python 中核心的编码规范、代码检查与格式化工具等几个方面帮助大家掌握 Python 编码规范。

为什么我们需要编码规范?

我们都知道在编写代码的过程中,遵循正确的语法便可以写出能够正常运行的程序,那么为什么还需要编码规范的约束呢?这是因为编码规范往往和代码质量、可读性、可维护性,甚至软件的生命周期都有直接的关系,遵循编码规范,会给我们在代码的开发以及后期维护带来很多好处,包括但不限于以下几点:

  • 有助于增强代码的一致性和可读性。代码被阅读的次数远大于它被编写的次数,良好的遵循编码规范可以保证代码在一个项目中,甚至多个项目之间保持一致性和可读性;
  • 有助于提高代码的可维护性和代码质量。易于理解的变量名称,清晰的代码布局,风格一致的注释等,都有助于降低开发和维护的难度,减少由于不遵循规范而产生的晦涩难懂的代码,并降低 bug 出现的可能性;
  • 有助于提高软件团队的开发和协作效率。团队成员之间可以快速的阅读和理解对方所编写的代码,将更多的时间和精力投入更加核心的业务中去;
  • 有助于提高项目和产品的交付质量。团队的效率和代码的质量,往往影响着项目的最终结果,也是产品质量与市场竞争力的决定性因素之一。

PEP - Python Enhancement Proposals

什么是 PEP?

PEP 的英文全称是 Python Enhancement Proposals,即 Python 增强提案,它主要用于提出 Python 新特性、收集社区对于某些问题的讨论、记录 Python 核心的设计决策等。 在形式上来说,PEP 是一种技术文档;在内容上来说,一般会包括提供给 Python 社区的信息、新特性的功能及原理描述等内容。

PEP的分类

PEP按照其作用可分为以下三种:

  • 标准类 PEP(Standards Track PEP):主要用于描述 Python 的新特性或实现等。这类 PEP 的数量在所有 PEP 中占比最多,比如列表推导 PEP 202 以及引起争议的表达式内赋值 PEP 572 等。

  • 信息类 PEP(Informational PEP):主要用于提供一般性的指导原则、信息等。比如著名的 Python 之禅 PEP 20 等。

  • 流程类 PEP(Process PEP):主要围绕 Python 相关流程,适用于 Python 语言本身以外的领域。比如下文即将介绍的 Python 编码规范 PEP 8 等。

PEP 8 - Style Guide for Python Code

PEP 8 的英文全称为 Style Guide for Python Code ,即 Python 编码风格指南(或称规范),这份指南涵盖三个大的方面,代码布局、注释文档以及命名规范。其主要包括以下内容:

  • 代码的整体布局,比如缩进、行最大长度、换行与空行、导入语句的位置及组织结构、编码声明、dunder 方法位置等;
  • 代码中的引号以及空格、行尾部逗号;
  • 复合语句的基本结构;
  • 注释编写的基本规范,主要包括块注释、行内注释和文档字符串;
  • 针对变量、方法、函数、类等元素的命名规范。

接下来,我们便根据 PEP 8 ,同时结合一些示例代码分别对上面提到的内容来进行了解。

代码的整体布局

代码的整体布局主要囊括了代码在整体结构上应该注意的事项,比如用来区分代码块层次的缩进应使用 4 个空格、代码中函数与类之间应使用两个空行进行分隔等。良好的遵守这些布局规范,有助于代码在整体结构上更加清晰易读,从结构上也有助于对代码逻辑的组织。

缩进与空格 / 制表符

Python 在语法上使用缩进来确定代码块的开始和结束,对于每一级缩进,应为 4 个空格,并且不要混用空格与制表符(在 Python 3 中从语法层面不允许混用空格和制表符,如果混用后会抛出异常:TabError: inconsistent use of tabs and spaces in indentation )。需要注意的是,缩进本身是一种语法上的限制,是强制性的,而缩进采用空格还是制表符,采用空格时空格的数量应为多少,这些在语法上是不限制的。另外,我们可以在 PyCharm 中进行 tab 以及显示空格的设置,另外在下图中,我们也可以勾选 Show method separators ,这会有助于我们更容易的对代码结构的区分和阅读。

图片描述

图片描述
当需要换行时,换行后的内容应垂直对齐被包裹的元素。通常,换行时的缩进有两种建议的方式,一种是使用括号内的隐式换行形式,比如下面这样:

def function_name(var_one, var_two, 
                  var_three, var_four):
    pass

另外一种是像下面这样使用悬挂缩进。需要注意的是,使用悬挂缩进时第一行不应放置元素,并且当元素与下面内容的行相同缩进层次时,可以增加缩进来区分。

# 第一行不应放置元素
function_name(
    1, 2,
    3, 4)

# 元素与下面的行相同缩进时,应增加缩进进行区分
def function_name(
        var_one, var_two,
        var_three, var_four):
    print(var_one, var_two, var_three, var_four)  

当 if 等控制语句的条件部分需要换行时,可以使用括号将条件部分包裹起来,在括号内进行换行。

if (condition_one
        and condition_two):
    pass

当多行结构在结束时,其右括号可以另起一行与前一行元素的第一个字符对齐或与第一行元素第一个字符对齐。

one_list = [
    1, 2, 3,
    4, 5, 6
	]
two_list = [
    1, 2, 3,
    4, 5, 6
]

行最大长度

代码中所有行的最大长度应限制在 79 个字符以内,文档字符串和注释的最大长度应限制在 72 个字符以内。限制最大长度的主要好处在于它使得代码更加紧凑可读,并且有利于代码审阅( code review ),可以方便的并排打开两个版本的文件。更加详细的原因在 PEP 8 的原文中进行了描述:

Limiting the required editor window width makes it possible to have several files open side-by-side, and works well when using code review tools that present the two versions in adjacent columns.

The default wrapping in most tools disrupts the visual structure of the code, making it more difficult to understand. The limits are chosen to avoid wrapping in editors with the window width set to 80, even if the tool places a marker glyph in the final column when wrapping lines. Some web based tools may not offer dynamic line wrapping at all.

这一规范对于标准库来说是保守的,通常标准库的代码必须遵守 PEP 8 中关于行最大长度的限制。但在某些公司或者团队内部,这一项规范可能会对做一定的进行调整(我们会在后面讨论关于团队内部的一致性和 PEP 8 的结合),比如将限制的最大字符数适当的扩大一些,PEP 8 中推荐的调整为 99 字符,但文档和注释的长度应仍然保持在 72 字符以内。作为一个题外话,最大长度限制这一规范很有可能会在未来的 Python 4.0 中进行适当修改。

另外,也可以在 PyCharm 中进行设置来调整长度限制的参考值。其中 hard wrap 意为硬换行,其会在格式化时强制插入换行符(若勾选了 Wrap on typing 则会在输入时动态的换行),与 hard wrap 对应的便是 soft wrap ,即只在 IDE 的显式效果上进行换行,并不会真正的插入换行符。

图片描述

soft warp 在 PyCharm 中的设置有两种方式,分别为:针对单个文件 View > Active Editor > Use Soft Wraps; 针对整个项目 Settings > Editor > General > Soft Wraps。

图片描述

当一个代码行的长度超过限制时应进行换行,换行应优先使用括号内隐式换行,其次才是使用反斜杠。

# 应优先使用括号内隐式换行
if (condition_one
        and condition_two):
    pass

# 不提倡下面的方式
if condition_one \
        and condition_two:
    pass

但在一下场景下,不能使用隐式换行,比如下面 with 中有多个语句时,可使用反斜杠。

with open(path_one) as file_one, \
        open(path_one) as file_two:
    pass

运算符与换行、代码之间的空行

我们可以在二元运算符之前或之后进行换行,推荐的方式是在二元运算符之前换行,像下面这样:

result = (var_one
          - var_two
          + (var_three * var_four)
          - var_five)

函数与类之间应用两个空行隔开,类中的方法之间应用一个空行隔开,函数或方法中的不同功能的代码块可使用空行隔开。

导入的组织

导入的位置应在该文件头部,位于模块注释和文档字符串之后,全局变量和常量之前。针对不同包或模块的导入,在代码结构上应该位于不同的行。针对同一个包或模块中进行的多个导入,可以位于同一行。如果你的代码中有多个导入,那么组织导入的整体顺序应首先为标准库导入,接着是相关第三方库导入,最后是本地代码导入。这三个部分之间可以用一个空行隔开。我们下面的示例摘自 Tornado 中 tornado.concurrent 的源码,并进行了一定省略,可以清楚的看到关于代码中导入的位置与组织形式。

#
# Copyright 2012 Facebook
#
# 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
# ...
"""Utilities for working with ``Future`` objects.

Tornado previously provided its own ``Future`` class, but now uses
`asyncio.Future`. This module contains utility functions for working
with `asyncio.Future` in a way that is backwards-compatible with
Tornado's old ``Future`` implementation.
...
"""

import asyncio
from concurrent import futures
import functools
import sys
import types

from tornado.log import app_log

import typing
from typing import Any, Callable, Optional, Tuple, Union

_T = typing.TypeVar("_T")


class ReturnValueIgnoredError(Exception):
    # No longer used; was previously used by @return_future
    pass
# ...

对于导入的形式,选择 import <module>.<name>from <module> import <name>from .<module> import <name> 均可(前两种形式称为绝对导入,最后一种称为相对导入或显式相对导入)。推荐优先使用绝对路径进行导入(即绝对导入,absolute imports ),绝对导入的方式更具可读性,并且在导入系统配置不正确时可以表现得更好,比如给出更加清晰可读的错误信息。在处理布局较为复杂的导入时,绝对路径的导入写法可能会造成导入语句过长,这时可考虑使用显式相对导入( explicit relative imports ),应避免使用隐式相对导入( implicit relative imports )。大家是否还记得上一小节中“Python 2.X 到 3.X 的主要修改”,其中提到了 Python 3.X 中已经不再支持隐式相对导入,这一点会在后面导入系统相关的小节中详细讲解。另外,我们应该尽量避免使用 from <module> import * 的通配符导入写法,通配符导入的写法会使当前的命名空间变得混乱,可能会造成不必要的混淆。 除此之外,在上一小节中我们也提到了 PyCharm 组织导入的快捷键 Ctrl + Shift + O ,它可以方便的帮我们调整导入的顺序,并去除一些无用导入。

代码的编码声明

在 Python 中 代码的编码声明指的是类似 # -*- coding: <encoding name> -*- 形式的声明(更多的声明形式,可以参考 PEP 263:Defining Python Source Code Encodings )。Python 解释器会根据该声明来读取源代码,比如我们在 Python 2.X 中常用的 # -*- coding: utf-8 -*- 。另外,对于 Python 2.X 中使用 ASCII 编码、在 Python 3.X 中使用 UTF-8 编码的代码来说,通常不需要编写编码声明(但为了兼容性考虑,目前在多数情况下 Python 3.X 中也会使用 UTF-8 编码声明)。

dunder 方法的位置

首先,我们来说明 dunder 的含义,它是一个合成词,即双下划线 double-underscore 。dunder 方法便指的是 Python 中的特殊方法,也可称为魔术方法,这些方法以双下划线开始和结尾,在运算符重载、数据结构定义中起着重要的作用。对于我们所编写的常规方法来说,不应使用这种双下划线开始和结尾的形式。

首先对于模块级别的 dunders,类似 __all____author____version__ 等方法 ,其位置应位于文档字符串之后,除 from __future__ 之外的导入语句之前(future 语句(from __future__ import ... )的作用是在某个模块中指定使用未来某个 Python 新版本中的特性,主要用于版本之间的过渡,降低解决版本不兼容导致迁移问题的难度。比如在Python 2.X 代码中常用的 from __future__ import unicode_literals,它的作用便是将该模块中的所有字符串处理为 unicode 字符串。)。其次在类级别中,dunders 应该放置在一个类定义的开始位置。对于一些常见的特殊方法作用,我们会在面向对象的小节中了解。

"""
module docstrings
"""

from __future__ import unicode_literals

__all__ = ['testa', 'testb']
__version__ = '1.0'
__author__ = 'zhangsan'

import json

引号、空格与逗号

单引号还是双引号?

在 Python 中,对单引号和双引号没有特殊的使用要求,它们的作用是相同的。

# 当一个字符串需要包含单或双引号时,应使用与最外层不同的引号,来避免进行转义(转义会降低代码的可读性)
my_name = 'my name is "python"'

# 不提倡下面的方式
my_name = 'my name is \'python\''

表达式和语句中的空格

在多数场景中不应使用无关的空格

在右括号前、左括号后、逗号、分号、冒号之前都不应使用无关空格:

function_name('test', [1, 2, 3])

# 不提倡下面的方式
function_name( 'test' , [1, 2, 3] )

在切片操作中,冒号两边的空格数量应相同;在扩展切片(Extended Slices ,是指在切片中使用可选的第三个参数 step,可称之为步长,默认为 1 )中,两个冒号应使用相同的空格数量;当切片中的某个参数被省略时,对应位置不应使用空格。在下面的例子中,大家可能会发现某些 PEP 8 提倡的用法,在 PyCharm 中会被提示 “whitespace before ‘:’ ”,这是由于 PyCharm 默认检查工具 pycodestyle.pypep8.py )的问题导致的,详情可以参考该 issue False positive "E203 whitespace before ‘:’ " on list slice

my_list[1:6], my_list[var_one + var_two : var_three + var_four]
my_list[1:6:2], my_list[: func_one(var_one) : func_two(var_two)]

# 不提倡下面的方式
my_list[1 :6], my_list[var_one + var_two:var_three + var_four]
my_list[1:6 :2], my_list[ : func_one(var_one) : func_two(var_two)]

对于扩展切片的示例如下:

# 下面的代码在交互环境中完成(可在 PyCharm 中左下角打开 Python Console 或者使用 IPython 等工具)
>>> my_list = [1, 2, 3, 4, 5, 6, 7, 8]
>>> my_list[1:6]
[2, 3, 4, 5, 6]
# 扩展切片操作,步长值为2,其作用是按照步长值为间隔选取元素,其结果是选取出了具有偶数索引的元素
>>> my_list[1:6:2]  
[2, 4, 6]

在函数名称与参数列表之间不应使用无关空格;在索引或切片中,括号和字典 / 列表等对象之间不应使用无关空格。

function_name(var_name)
my_list[1]
my_dict['key']

# 不提倡下面的方式
function_name (var_name)
my_list [1]
my_dict ['key']

在赋值语句中,不应为了对齐赋值语句而使用无关空格。在表达式或语句的尾部不应使用无关空格,尾部的空格会在某些场景下造成问题,比如在使用反斜杠进行换行时,在反斜杠后添加空格,会引发 SyntaxError: unexpected character after line continuation character 的异常。

var_one = 1
long_var_two = 2

# 不提倡下面的方式
var_one      = 1
long_var_two = 2

在函数中,对于形参列表中的默认参数以及函数调用时的关键字参数,其 = 两侧不应使用无关空格。

def function_one(var_one=True):
    return var_one


function_one(var_one=False)

在运算符两侧合理使用空格

在赋值、增强型赋值、比较、布尔等二元运算符两侧应使用空格。

var_one = 1
var_two = var_one + 1
var_one += 1

在同时使用不同优先级的运算符时,可在最低优先级的运算符两侧使用空格。对于一些比较复杂的场景下,需要我们来自己定夺空格的使用。

var_two = var_one*3 + 1
var_five = var_one*var_two + var_three*var_four

# 注意下面的情况,PEP 8 把这种情况交由我们自己定夺,但仍给出了类似下面的示例供我们参考
var_five = (var_one+var_two) * (var_three+var_four)

# 不提倡下面的方式
var_five = (var_one + var_two) * (var_three + var_four)

在函数注解中合理使用空格

在函数注解中,冒号前不应使用无关空格,-> 两侧应使用空格。

def function_one(var_one: bool) -> int:
    if var_one:
        ret = 1
    else:
        ret = -1
    return ret

当函数注解和默认参数同时存在于同一参数时,在 = 两侧应使用空格。

def function_one(var_one: bool = True, var_two=False) -> int:
    pass

函数注解( Function annotations )是 Python 3 提供的一种可选的语法,可以对函数的参数和返回值进行注解。在该特性在 3.0 中被加入后,官方并未给出其明确的语义,随着社区中相关经验的积累以及官方相关新特性的释放,函数注解目前主要用于类型提示,常结合 typing 模块使用,更多细节我们会在后面的小节中讲解。

行尾部的逗号

当你想要获得包含一个元素的元组时,应在表达式尾部添加逗号,并最好将其包裹在圆括号内。

var_one = (1,)

# 最好不使用下面的方式
var_one = 1,

对于某些包含参数并需要不断扩展的序列,在添加每个参数时,在其尾部也可添加逗号,这样可以规避一些难以发现的问题(请不要小看下方代码所示的问题,当它隐藏在某一段代码中时,你就会感到十分头疼)。

# 当在新行中添加参数时,在尾部添加逗号有利于后续扩展,比如可避免漏写上一行结尾处逗号的问题
BACKENDS = (
    'BackendOne',
    'BackendTwo',
	)
# 在扩展时若漏写第二行结尾处的逗号,IDE 中也不会有任何提醒,但会导致无法正常使用该数据
BACKENDS = (
    'BackendOne',
    'BackendTwo'  
    'BackThree'
	)

复合语句

复合语句是指包含其他语句的语句,比如函数和类的定义、ifwhilefortrywith 等语句,不应将整个复合语句或复合语句中的一部分放置在一行(虽然有时我们仍可在一些场合会看到这种情况)。

if var_one == 1:
    pass

# 不提倡下面的方式
if var_one == 1: pass

到这里,我们对 Python 编码规范中关于代码的主要结构部分的讲解已经基本完成了,在下半部分中,我们会重点对 Python 中的注释和命名规范进行了解。

}
限时优惠 ¥ 58.00 ¥ 78.00

你正在阅读课程试读内容,订阅后解锁课程全部内容

千学不如一看,千看不如一练

手机
阅读

扫一扫 手机阅读

Python 核心技术精讲
限时优惠 ¥ 58.00 ¥ 78.00

举报

0/150
提交
取消