跳至內容

使用者: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 是 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 或其子类,即任意类型