User:Xyy23330121/Python/函数注释


如同本学习资料第一次提到注释时一样:在编写程序时,程序员会不可避免地忘记之前写的内容。为了让程序员在读已经忘记的函数代码时,可以快速了解函数的内容、使用要点以及可能的、修改函数的方法。需要进行注释。

本章学习的注释,也可以由功能比较强劲的文本编辑器读取、并在使用函数时实时地给程序员以提示。

由于读者在撰写的代码较少时,并没有作注释的必要,从而学习动力可能不足。本章为选学。


文档字符串 编辑

可以通过以下方式,写入和输出文档字符串。

def func():
    """什么都不做

  本函数仅包含一个 pass 语句。
  
  当使用 if / else / def 等语句时,冒号
  的下一行必须是语句组的内容,要进行缩进。
  但单独只有缩进也会导致出错。因此,必须
  要写入至少一个语句。若希望语句组不执行
  任何操作,可以使用 pass 语句来作为语句
  组中唯一的语句。
  
  在使用 pass 语句时,应注意能否有更好的
  方法。比如 if True: pass; else: do so-
  mething的情况。可以直接用 if not True。
"""
    pass

print(func.__doc__)  #输出文档字符串的内容

输出为:

什么都不做

  本函数仅包含一个 pass 语句。
  
  当使用 if / else / def 等语句时,冒号
  的下一行必须是语句组的内容,要进行缩进。
  但单独只有缩进也会导致出错。因此,必须
  要写入至少一个语句。若希望语句组不执行
  任何操作,可以使用 pass 语句来作为语句
  组中唯一的语句。
  
  在使用 pass 语句时,应注意能否有更好的
  方法。比如 if True: pass; else: do so-
  mething的情况。可以直接用 if not True。

我们接下来学的类 (class) 也可以使用文档字符串,写入和输出的方法是相同的。使用文档字符串时,应当注意其可读性。

函数的类型注解 编辑

除上面的“文档字符串”以外,类型注解也是一种良好的注释方式。

对于参数的类型注解如果有不了解的地方,可以查阅 Python 文档。如果查阅 Python 文档之后还不了解,那就直接省略注解也是可以的。

简单类型注解 编辑

类型名称列表
类型名 含义
int 整数
float 浮点数
complex 复数
bool 布尔值
list 列表
tuple 元组
str 字符串
dict 字典
set 集合
frozenset 不可变集合

以下示例展示了对参数的类型进行注解,以及设置默认值的方式。该示例还提示了该函数返回值的类型。

def plus(arg1: int, arg2: int = 0) -> int:
    return arg1 + arg2

这代表着:plus 函数的参数 arg1 应传入一个 int 类型的值;arg2 有默认值 0,如果要传入,应传入一个 int 类型的值。plus 函数应返回一个 int 类型的值。

类型注解仅作为标识,对函数运行无影响。如果我们执行:

print(plus(1.1))

输出为:

1.1

尽管类型注解提示了应当给参数传入整数,但传入浮点数时,函数依旧正常运行。而函数的返回值也并非类型注解提示的整数。

右侧表格简单总结了目前学过的一些类型的名称。读者可以查阅 Python 文档,了解更多的内置类型。

关于类型名称,我们将在“类”章节中得到更深刻的了解。

类型注解与函数的属性 编辑

类型注解是函数的一个属性。我们可以用以下方式输出注解:

print(plus.__annotations__)

注解的输出为一个字典:

{'arg1': <class 'int'>, 'arg2': <class 'int'>, 'return': <class 'int'>}

类型注解的作用 编辑

静态类型检查器 编辑

类型注解可以用于静态类型检查器。静态类型检查器会检查 Python 代码中各元素的类型,并对于其中类型不正确的内容进行提示和报错。比如:

def func(s: int) -> None:
    s[index]

在静态类型检查器中会提示报错,因为整数类型不支持索引操作。

文本编辑器 编辑

一些功能强大的文本编辑器会按照类型来提供提示,比如,输入以下内容后:

a = [0, 1]  #文本编辑器检查这一行,得知 a 的类型
a.

文本编辑器会自动在后面添加一个下拉的提示框。以提示之后的内容可以写appendclearcopy等。

但这对于函数的参数是没用的,

def func(a):
    a.

此时,文本编辑器不会提供任何提示。这是因为文本编辑器不能通过赋值表达式知道函数参数的类型。

但如果我们使用:

def func(a:list):  #文本编辑器检查这一行,得知 a 的类型
    a.

文本编辑器就能够提供提示了。这在许多情况下很有帮助。

类型别名 编辑

有些类型,尤其是复合类型写起来比较麻烦。此时,我们可以用以下几种方式创建类型别名:

type 语句 编辑

type vector = list[float]

之后,我们就可以使用名称 vector 进行类型注解。

简单赋值语句 编辑

type 语句是 Python 3.12 新增的。为了向下兼容,也可以通过简单赋值语句创建类型别名:

vector = list[float]

特殊类型 编辑

None 编辑

我们之前提到过,None 是 Python 中代表“无”的量。实际上,它同时也是一个类型。我们可以用 None 来标记函数没有返回值。例如:

def prtplus(arg1: int, arg2: int = 0) -> None:
    print(arg1 + arg2)

typing.Any 编辑

该类型代表任意类型。它可以说明函数参数可以传入任意类型,也可以说明函数可以返回任意类型。例如:

from typing import Any

def prttype(arg: Any) -> None:
    print(type(arg))

特别的,对于未进行类型注解的参数或返回值,默认使用 typing.Any 作为其参数类型和返回值类型。

抽象基类 编辑

Python 提供了多种抽象基类(Abstract Base Class,在 Python 文档中简称为 ABC)。这些基类也可以用于判断类型。

比如,collections.abc.Mapping 表示映射类型,任何具有映射功能的类型,比如字典,都会被判断为 collections.abc.Mapping 的子类型,也就可以用该类型作为标注:

from collections.abc import Mapping

def func(arg: Mapping) -> None: pass

为了让代码的类型在进行检查时尽可能地适用广泛,可以使用这种抽象类型。

对于 collections.abc 提到的“抽象方法”等内容,可能要到类的方法与Python的计算章节才会详细讲解。

联合类型 编辑

可以用|运算符,表示类型是左右两者之一。

R = int | float           #整数或浮点数
C = int | float | complex #整数或浮点数,或复数

也可以用泛型的方法来作标注:

from typing import Union
R = Union[int, float]           #整数或浮点数
C = Union[int, float, complex]  #整数或浮点数,或复数

泛型 编辑

序列类型、字典、集合等被称之为容器类型。许多容器类型都支持下标操作,以表示其内部元素的类型。这种下标操作所得到的结果,称之为泛型。

我们可以用以下方式标注“由某个类型的元素”组成的列表:

a = list[int]  #以多个整数组成的类型。
b = list[int | None]  #以多个“整数或None”组成的类型

类似,可以用 set[int] 来表示由多个整数组成的列表等。


而对于映射结构,比如 dict ,则是用 dict[str, int] 表示“用字符串作索引,得到整数”的字典。


对于 Python 中的大多数容器,类型系统会假定容器中的所有元素都是相同类型的。这表现在代码上,就是 list[ClassName] 等方式最多仅支持一个参数。由此,我们可以总结出泛型的一般形式:

  1. 一般容器类型:list[int] set[int]
  2. 映射容器类型:dict[str, int] collections.abc.Mapping[str, int]

但元组是一个例外。元组中的元素,在许多代码中并非相同类型,且其位置很关键。所以,我们有以下特例:

特例:元组类型 编辑

可以用以下方式标注“由特定类型元素”组成的元组:

a = tuple[int, str]  #有二个元素的元组,第一个元素是整数,第二个元素是字符串。

为何使用泛型 编辑

之所以使用泛型,是因为没有很好的方法对容器中元素的类型进行判断。于是,在输入:

def func(a: list):
    a[0].

之后,哪怕使用了功能强大的文本编辑器,也不会弹出任何关于 a[0] 所属类型的提示。

而如果输入:

def func(a: list[complex]):
    a[0].

文本编辑器就可以进行提示了。它会提示之后的内容可以写conjugateimagreal等。

泛型的使用 编辑

泛型在使用上和构造出泛型的原类型毫无区别。

array = list[int] #列表泛型
a = array('Python')  #等同于 a = list('Python')
print(a, type(a)) #输出:['P', 'y', 't', 'h', 'o', 'n'] <class 'list'>

可调用类型 编辑

函数就是可调用的类型。我们可以通过以下方式标注可调用的类型。

from typing import Callable

a = Callable[[int], str]       #输入整数参数,返回字符串的可调用类型
b = Callable[[int, int], None] #输入两个整数参数,不返回任何值的可调用类型

特别的,用省略号(三个小数点)可以表示任意参数列表。比如:

from typing import Callable

a = Callable[..., str]       #输入任意参数列表,返回字符串的可调用类型

类对象的类型 编辑

利用 type[ClassName] 的方式,可以要求传入的参数或返回值的类型是“类”本身或其子类本身。我们在之前的学习中,已经遇到过“类”对象了。比如:

1       #类型为:int
type(1) #类型为:type[int]
int     #类型为:type[int]

在使用时,可以用以下方式:

a = type[int]    #类 int 或其子类
b = type[object] #类 object 或其子类,即任意类型