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 或其子类,即任意类型