Python 3.12 有什麼新功能

編輯者:

Adam Turner

本文介紹了 Python 3.12 與 3.11 相比多了哪些新功能。Python 3.12 於 2023 年 10 月 2 日發布。完整詳情請見 changelog

也參考

PEP 693 -- Python 3.12 發布時程

發布重點摘要

Python 3.12 是 Python 程式語言的穩定版本,包含了語言和標準函式庫的綜合變更。標準函式庫的變更主要集中在清理棄用的 API、可用性和正確性。值得注意的是,distutils 套件已從標準函式庫中移除。ospathlib 中的檔案系統支援進行了許多改進,並且有數個模組得到更好的效能。

語言變更主要集中在可用性,因為 f-字串已經移除了許多限制,並且持續改進 'Did you mean ...' 建議。新的型別參數語法type 陳述式改進了泛型型別型別別名在與靜態型別檢查器一起使用上的效率。

這篇文章並不試圖提供所有新功能的完整規格,而是提供一個方便的概覽。完整詳情應參考文件,如標準函式庫參考語言參考。如果你想了解某個新功能的完整實作和設計理念,請參考該功能的 PEP;但請注意 PEP 通常在功能完全實作後就不再更新。


新增語法特性:

新增語法特性:

直譯器改進:

Python 資料模型改進:

標準函式庫中的顯著改進:

安全性改進:

  • 將於 hashlib 中 SHA1、SHA3、SHA2-384、SHA2-512 和 MD5 內建實作替換為來自 HACL* 專案的經正式驗證的程式碼。這些內建實作仍然作為備援方案 (fallback),僅在 OpenSSL 不提供它們時使用。

C API 改進:

  • PEP 697,不穩定 C API 層

  • PEP 683,不滅物件 (immortal objects)

CPython 實作改進:

  • PEP 709,行內綜合運算 (comprehension inlining)

  • 對 Linux perf 分析器的 CPython 支援

  • 在支援的平台上實作堆疊溢位保護

新增型別特性:

重要的棄用、刪除或限制:

  • PEP 623:從 Python C API 中的 Unicode 物件中刪除 wstr,將每個 str 物件的大小減少至少 8 個位元組。

  • PEP 632:刪除 distutils 套件。請參閱遷移指南以查看替換原有 API 的建議。如果你在 Python 3.12 及後續版本中仍然需要它們,第三方套件 Setuptools 會繼續提供 distutils

  • gh-95299:不再於以 venv 建立的虛擬環境中預先安裝 setuptools。這意味著 distutilssetuptoolspkg_resourceseasy_install 將預設不可用;若要使用這些工具,請在已啟用的虛擬環境中執行 pip install setuptools

  • asynchatasyncoreimp 模組以及幾個 unittest.TestCase方法別名已被刪除。

新增功能

PEP 695:型別參數語法

PEP 484 下的泛型類別和函式是使用較冗長語法來宣告,這使得型別參數的作用域不明確,並且需要顯式地宣告變異數 (variance)。

PEP 695 引入了一種新的、更簡潔和顯式的方法來建立泛型類別函式

def max[T](args: Iterable[T]) -> T:
    ...

class list[T]:
    def __getitem__(self, index: int, /) -> T:
        ...

    def append(self, element: T) -> None:
        ...

此外,PEP 引入了一種使用型別陳述式來宣告型別別名的新方法,該方法會建立一個 TypeAliasType 的實例:

type Point = tuple[float, float]

型別別名也可以是泛型

type Point[T] = tuple[T, T]

新的語法允許宣告 TypeVarTupleParamSpec 參數,以及帶有邊界 (bounds) 或限制 (constraints) 的 TypeVar 參數:

type IntFunc[**P] = Callable[P, int]  # ParamSpec
type LabeledTuple[*Ts] = tuple[str, *Ts]  # TypeVarTuple
type HashableSequence[T: Hashable] = Sequence[T]  # 帶有邊界的 TypeVar
type IntOrStrSequence[T: (int, str)] = Sequence[T]  # 帶有限制的 TypeVar

透過此語法建立之型別別名的值和型別變數的邊界和限制,僅會根據需求來求值(請參閱延遲求值 (lazy evaluation))。這意味著型別別名可以參照在檔案中後續定義的其他型別。

透過型別參數串列宣告的型別參數在宣告作用域及任何巢狀作用域內皆為可見,但在外部作用域內不可見。例如,它們可以用在泛型類別方法的型別註釋中或類別主體中。但是,在定義類別後,它們不能在模組作用域內使用。有關型別參數的 runtime 語義的詳細描述,請參閱 Type parameter lists

為了支援這些作用域語義而引入了一種新的作用域,即註釋作用域 (annotation scope)。註釋作用域的行為在很大程度上類似於函式作用域,但與封閉類別作用域的交互方式不同。在 Python 3.13 中,註釋也將在註釋作用域內進行求值。

詳情請見 PEP 695

(PEP 由 Eric Traut 撰寫。由 Jelle Zijlstra、Eric Traut 和其他人在 gh-103764 中實作。)

PEP 701:f 字串的語法形式化

PEP 701 解除了 f 字串在使用上的一些限制。f 字串內的運算式元件現在可以是任何有效的 Python 運算式,包括重複使用與包含 f 字串相同的引號的字串、多行運算式、註解、反斜線和 unicode 轉義序列。讓我們詳細介紹一下這些內容:

  • 引號重用:在 Python 3.11 中,重複使用與封閉的 f 字串相同的引號會引發 SyntaxError,強制使用者使用其他可用的引號(例如,如果 f 字串使用單引號,則使用雙引號或三引號)。在 Python 3.12 中,你現在可以執行以下操作:

    >>> songs = ['Take me back to Eden', 'Alkaline', 'Ascensionism']
    >>> f"This is the playlist: {", ".join(songs)}"
    'This is the playlist: Take me back to Eden, Alkaline, Ascensionism'
    

    請注意,在此更改之前,對於如何嵌套 f 字串沒有明確的限制,但事實上字串引號不能在 f 字串的運算式元件內重複使用,因此無法任意嵌套 f 字串。事實上,這是嵌套層數最多的 f 字串:

    >>> f"""{f'''{f'{f"{1+1}"}'}'''}"""
    '2'
    

    由於現在 f 字串可以在運算式元件內包含任何有效的 Python 運算式,因此現在可以任意嵌套 f 字串:

    >>> f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}"
    '2'
    
  • 多行運算式和註解:在 Python 3.11 中,f 字串運算式必須在單行中定義,即使 f 字串中的運算式通常可以跨越多行(就像在多行上定義的 list 常值一樣),從而使它們更難閱讀。在 Python 3.12 中,你現在可以定義跨越多行的 f 字串,並新增內嵌註解:

    >>> f"This is the playlist: {", ".join([
    ...     'Take me back to Eden',  # My, my, those eyes like fire
    ...     'Alkaline',              # Not acid nor alkaline
    ...     'Ascensionism'           # Take to the broken skies at last
    ... ])}"
    'This is the playlist: Take me back to Eden, Alkaline, Ascensionism'
    
  • 反斜線和 unicode 字元:在 Python 3.12 之前,f 字串運算式不能包含任何 \ 字元。這也影響了 unicode 轉義序列(例如 \N{snowman}),因為它們包含 \N 部分,該部分以前不能是 f 字串運算式元件的一部分。現在,你可以像這樣定義運算式:

    >>> print(f"This is the playlist: {"\n".join(songs)}")
    This is the playlist: Take me back to Eden
    Alkaline
    Ascensionism
    >>> print(f"This is the playlist: {"\N{BLACK HEART SUIT}".join(songs)}")
    This is the playlist: Take me back to Eden♥Alkaline♥Ascensionism
    

詳情請見 PEP 701

作為此功能實作方式的積極副作用(透過使用 PEG 剖析器 剖析 f 字串),現在 f 字串的錯誤訊息更加精確,並且包含錯誤的確切位置。例如,在 Python 3.11 中,以下 f 字串會引發 SyntaxError

>>> my_string = f"{x z y}" + f"{1 + 1}"
  File "<stdin>", line 1
    (x z y)
     ^^^
SyntaxError: f-string: invalid syntax. Perhaps you forgot a comma?

但錯誤訊息不包括行內錯誤的確切位置,並且還人為地將運算式用括號括起來。在 Python 3.12 中,由於使用 PEG 剖析器剖析 f 字串,因此錯誤訊息可以更精確並顯示整行:

>>> my_string = f"{x z y}" + f"{1 + 1}"
  File "<stdin>", line 1
    my_string = f"{x z y}" + f"{1 + 1}"
                   ^^^
SyntaxError: invalid syntax. Perhaps you forgot a comma?

(由 Pablo Galindo、Batuhan Taskaya、Lysandros Nikolaou、Cristián Maureira-Fredes 和 Marta Gómez 在 gh-102856 中貢獻。PEP 由 Pablo Galindo、Batuhan Taskaya、Lysandros Nikolaou 和 Marta Gómez 編寫)。

PEP 684:直譯器各別持有的 GIL

PEP 684 引入了直譯器各別持有的 GIL,因此子直譯器現在可以使用各個直譯器特有的 GIL 來建立。這使得 Python 程式可以充分利用多個 CPU 核心。目前這僅透過 C-API 使用,不過 Python API 預計在 3.13 中提供

使用新的 Py_NewInterpreterFromConfig() 函式建立一個具有自己的 GIL 的直譯器:

PyInterpreterConfig config = {
    .check_multi_interp_extensions = 1,
    .gil = PyInterpreterConfig_OWN_GIL,
};
PyThreadState *tstate = NULL;
PyStatus status = Py_NewInterpreterFromConfig(&tstate, &config);
if (PyStatus_Exception(status)) {
    return -1;
}
/* 新的直譯器現在會在目前執行緒中啟用。 */

關於如何使用子直譯器的 C API 搭配各直譯器獨立的 GIL 的更多範例,請參見 Modules/_xxsubinterpretersmodule.c

(由 Eric Snow 於 gh-104210 等貢獻。)

PEP 669:CPython 的低影響監控

PEP 669 定義了一個新的 API ,用於分析器、偵錯器和其他工具來監視 CPython 中的事件。它涵蓋了廣泛的事件,包括呼叫、回傳、行、例外、跳躍等等。這意味著你只需為所使用的功能承擔開銷,為接近零開銷的偵錯器和覆蓋率工具提供支援。有關詳細資訊,請參閱 sys.monitoring

(由 Mark Shannon 於 gh-103082 中貢獻。)

PEP 688:使緩衝區協定可在 Python 中存取

PEP 688 引入了一種從 Python 程式碼使用 緩衝區協定 的方法。實作 __buffer__() 方法的類別現在可用作緩衝區型別。

新的 collections.abc.Buffer ABC 提供了一種表示緩衝區物件的標準方法,例如在型別註釋中。新的 inspect.BufferFlags 列舉表示可用於自訂緩衝區建立的旗標。(由 Jelle Zijlstra 在 gh-102500 中貢獻。)

PEP 709:行內綜合運算

字典、串列和集合綜合運算現在是行內的,而不是為綜合運算的每次執行建立一個新的一次性函式物件。這可以將綜合運算的執行速度提高兩倍。請參閱 PEP 709 以了解更多詳細資訊。

綜合運算的疊代變數保持隔離,不會覆蓋外部作用域中的同名變數,綜合運算後它們也不可見。行內化確實會導致一些明顯的行為改變:

  • 回溯中不再有用於綜合運算的單獨堆疊框架,並且追蹤/分析不再將綜合運算顯示為函式呼叫。

  • symtable 模組將不再為每個綜合運算產生子符號表;相反,綜合運算的局部變數將包含在父函式的符號表中。

  • 現在,在綜合運算內呼叫 locals() 包含來自綜合運算外部的變數,並且不再包含用於綜合運算參數的合成 .0 變數。

  • 當在追蹤下執行(例如程式碼覆蓋率測量)時,直接疊代 locals()(例如 [k for k in locals()])的綜合運算可能會看到 "RuntimeError: dictionary changed size during iteration"。這與在 for k in locals(): 中已經看到的行為相同。為了避免錯誤,首先建立一個要疊代的鍵串列:keys = list(locals()); [k for k in keys]

(由 Carl Meyer 和 Vladimir Matveev 於 PEP 709 中貢獻。)

改善錯誤訊息

  • NameError 提升到頂層時,標準函式庫中的模組現在可能會被建議作為直譯器顯示的錯誤訊息的一部分。(由 Pablo Galindo 在 gh-98254 中貢獻。)

    >>> sys.version_info
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    NameError: name 'sys' is not defined. Did you forget to import 'sys'?
    
  • 改進實例的 NameError 例外的錯誤建議。現在,如果在方法中引發 NameError 並且該實例具有與例外中的名稱完全相同的屬性,則建議將包含 self.<NAME> 而不是方法作用域中的最接近匹配。(由 Pablo Galindo 在 gh-99139 中貢獻。)

    >>> class A:
    ...    def __init__(self):
    ...        self.blech = 1
    ...
    ...    def foo(self):
    ...        somethin = blech
    ...
    >>> A().foo()
    Traceback (most recent call last):
      File "<stdin>", line 1
        somethin = blech
                   ^^^^^
    NameError: name 'blech' is not defined. Did you mean: 'self.blech'?
    
  • 改進當使用者鍵入 import x from y 而不是 from y import x 時出現的 SyntaxError 錯誤訊息。(由 Pablo Galindo 在 gh-98931 中貢獻。)

    >>> import a.y.z from b.y.z
    Traceback (most recent call last):
      File "<stdin>", line 1
        import a.y.z from b.y.z
        ^^^^^^^^^^^^^^^^^^^^^^^
    SyntaxError: Did you mean to use 'from ... import ...' instead?
    
  • 由失敗的 from <module> import <name> 陳述式引發的 ImportError 例外現在包含基於 <module> 中可用名稱的 <name> 值的建議。(由 Pablo Galindo 在 gh-91058 中貢獻。)

    >>> from collections import chainmap
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ImportError: cannot import name 'chainmap' from 'collections'. Did you mean: 'ChainMap'?
    

其他語言更動

  • 現在,剖析器在剖析包含空位元組的源程式碼時會引發 SyntaxError。(由 Pablo Galindo 在 gh-96670 中貢獻。)

  • 不是有效轉義序列的反斜線字元對現在會產生 SyntaxWarning,而不是 DeprecationWarning。例如,re.compile("\d+\.\d+") 現在發出一個 SyntaxWarning"\d" 是無效的轉義序列,請使用原始字串作為正規表示式:re.compile(r"\d+\.\d+"))。在未來的 Python 版本中,最終將引發 SyntaxError,而不是 SyntaxWarning。(由 Victor Stinner 在 gh-98401 中貢獻。)

  • 值大於 0o377 的八進位轉義符(例如:"\477")在 Python 3.11 中已棄用,現在會生成 SyntaxWarning,而不是 DeprecationWarning。在未來的 Python 版本中,它們最終將成為 SyntaxError。(由 Victor Stinner 在 gh-98401 中貢獻。)

  • 綜合運算目標部分中使用的未儲存的變數現在可以在賦值運算式中使用 (:=)。例如,在 [(b := 1) for a, b.prop in some_iter] 中,現在允許對 b 進行賦值。請注意,根據 PEP 572 ,仍然不允許對儲存在綜合運算目標部分中的變數進行賦值(如 a)。(由 Nikita Sobolev 在 gh-100581 中貢獻。)

  • 類別或型別的 __set_name__ 方法中引發的例外不再由 RuntimeError 包裝。上下文資訊會以 PEP 678 註解加入例外。(由 Irit Katriel 在 gh-77757 中貢獻。)

  • try-except* 構造處理整個 ExceptionGroup 並引發另一個例外時,該例外不再包裝在 ExceptionGroup 中。在 3.11.4 版本中也進行了更改。(由 Irit Katriel 在 gh-103590 中貢獻。)

  • 垃圾收集器現在僅在 Python 位元組碼評估迴圈的 eval 斷路器機制上運行,而不是在物件分配上運行。GC 也可以在呼叫 PyErr_CheckSignals() 時運行,因此需要長時間運行而不執行任何 Python 程式碼的 C 擴充也有機會定期執行 GC。(由 Pablo Galindo 在 gh-97922 中貢獻。)

  • 所有需要布林參數的內建和擴充可呼叫物件現在接受任何型別的參數,而不僅僅是 boolint。(由 Serhiy Storchaka 在 gh-60203 中貢獻。)

  • memoryview 現在支援半浮點型別("e" 格式碼)。(由 Donghee Na 和 Antoine Pitrou 在 gh-90751 中貢獻。)

  • slice 物件現在是可雜湊的,允許它們作為字典的鍵和集合的項。(由 Will Bradshaw、Furkan Onder 和 Raymond Hettinger 在 gh-101264 中貢獻。)

  • sum() 現在使用 Neumaier 求和來提高對浮點數或混合整數和浮點數求和時的準確性和交換性。(由 Raymond Hettinger 在 gh-100425 中貢獻。)

  • 現在,在剖析包含空位元組的源程式碼時,ast.parse() 會引發 SyntaxError,而不是 ValueError。(由 Pablo Galindo 在 gh-96670 中貢獻。)

  • tarfileshutil.unpack_archive() 中的提取方法有一個新的 filter 參數,該參數允許限制可能令人驚訝或危險的 tar 功能,例如在目標目錄之外建立檔案。有關詳細資訊,請參閱tarfile 提取過濾器。在 Python 3.14 中,預設將切換為 'data'。(由 Petr Viktorin 在 PEP 706 中貢獻。)

  • 如果底層對映是可雜湊的,則 types.MappingProxyType 實例現在是可雜湊的。(由 Serhiy Storchaka 在 gh-87995 中貢獻。)

  • 透過新的環境變數 PYTHONPERFSUPPORT 和命令列選項 -X perf 以及新的 sys.activate_stack_trampoline()sys.deactivate_stack_trampoline()sys.is_stack_trampoline_active() 函式,新增對效能分析器的支援。(由 Pablo Galindo 設計。由 Pablo Galindo 和 Christian Heimes 貢獻,Gregory P. Smith [Google] 和 Mark Shannon 在 gh-96123 中也有貢獻。)

新增模組

  • 無。

改進的模組

array

asyncio

  • asyncio 中寫入 socket 的效能得到了顯著提高。asyncio 現在可以避免在寫入 socket 時進行不必要的複製,並在平台支援的情況下使用 sendmsg()。(由 Kumar Aditya 在 gh-91166 中貢獻。)

  • 新增 asyncio.eager_task_factory()asyncio.create_eager_task_factory() 函式以允許選擇事件迴圈來執行急切任務,使某些用例速度提高 2 到 5 倍。(由 Jacob Bower 和 Itamar Oren 在 gh-102853gh-104140gh-104138 中貢獻)

  • 在 Linux 上,如果 os.pidfd_open() 可用且功能正常,asyncio 預設會使用 asyncio.PidfdChildWatcher 而非 asyncio.ThreadedChildWatcher。(由 Kumar Aditya 在 gh-98024 中貢獻。)

  • 事件迴圈現在會為每個平台使用最佳的子行程監看器 (child watcher)(如果支援則使用 asyncio.PidfdChildWatcher,否則使用 asyncio.ThreadedChildWatcher),因此不建議手動設定子行程監看器。(由 Kumar Aditya 在 gh-94597 中貢獻。)

  • asyncio.run() 新增 loop_factory 參數以允許指定自訂事件迴圈工廠函式。(由 Kumar Aditya 在 gh-99388 中貢獻。)

  • 新增 asyncio.current_task() 的 C 實作,加速了 4-6 倍。(由 Itamar Oren 和 Pranav Thulasiram Bhat 在 gh-100344 中貢獻。)

  • asyncio.iscoroutine() 現在為產生器回傳 False,因為 asyncio 不支援傳統的基於產生器的協程。(由 Kumar Aditya 在 gh-102748 中貢獻。)

  • asyncio.wait()asyncio.as_completed() 現在接受使用產生器來生成任務。(由 Kumar Aditya 在 gh-78530 中貢獻。)

calendar

csv

dis

  • 偽指令操作碼(由編譯器使用,但不會出現在可執行位元組碼中)現在在 dis 模組中公開。HAVE_ARGUMENT 仍然與真實操作碼相關,但對於偽指令沒有用處。請改用新的 dis.hasarg 集合。(由 Irit Katriel 在 gh-94216 中貢獻。)

  • 新增 dis.hasexc 集合來表示設定例外處理程序的指令。(由 Irit Katriel 在 gh-94216 中貢獻。)

fractions

importlib.resources

inspect

itertools

  • 新增 itertools.batched() 以將元素收集到大小均等的元組中,其中最後一批次可能比其他的少。(由 Raymond Hettinger 於 gh-98363 中貢獻。)

math

  • 新增 math.sumprod() 以計算乘積總和。(由 Raymond Hettinger 於 gh-100485 中貢獻。)

  • 擴充 math.nextafter() 來包含一個 steps 引數,用於一次向上或向下移動多步。(由 Matthias Goergens、Mark Dickinson 和 Raymond Hettinger 在 gh-94906 貢獻。)

os

  • 新增 os.PIDFD_NONBLOCK 以便在非阻塞模式下使用 os.pidfd_open() 為行程打開檔案描述器。(由 Kumar Aditya 在 gh-93312 中貢獻。)

  • os.DirEntry 現在包含一個 os.DirEntry.is_junction() 方法來檢查條目是否為連結點。(由 Charles Machalow 在 gh-99547 中貢獻。)

  • 在 Windows 上新增 os.listdrives()os.listvolumes()os.listmounts() 函式,用於列舉磁碟機 (drives)、磁碟區 (volumes) 和掛載點 (mount points)。(由 Steve Dower 在 gh-102519 中貢獻。)

  • os.stat()os.lstat() 現在在 Windows 上更加準確。st_birthtime 欄位現在將填充檔案的建立時間,st_ctime 已棄用,但仍包含建立時間(但將來將回傳最後一次元資料更改,以與其他平台保持一致)。根據你的檔案系統,st_dev 可能高達 64 位元、st_ino 高達 128 位元,並且 st_rdev 始終設定為零而不是錯誤的值。在較新版本的 Windows 上,這兩個功能可能會明顯更快。(由 Steve Dower 在 gh-99726 中貢獻。)

os.path

pathlib

platform

  • 新增對 Windows 11 和 2012 之後的 Windows Server 版本的偵測支援。之前,在比 Windows Server 2012 更新的 Windows Server 平台和 Windows 11 上的查詢會回傳 Windows-10。(由 Steve Dower 在 gh-89545 中貢獻。)

pdb

  • 新增方便的變數來暫時保存偵錯工作階段的值,並提供對當前幀或回傳值等值的快速存取。(由 Tian Gao 貢獻 gh-103693。)

random

shutil

  • shutil.make_archive() 現在將 root_dir 引數傳遞給支援它的自訂歸檔器。在這種情況下,它不再暫時將行程的當前工作目錄更改為 root_dir 來執行歸檔。(由 Serhiy Storchaka 在 gh-74696 中貢獻。)

  • shutil.rmtree() 現在接受一個新引數 onexc,它是一個類似於 onerror 的錯誤處理程序,但它需要一個例外實例而不是 (typ, val, tb) 三元組。onerror 已棄用。(由 Irit Katriel 在 gh-102828 中貢獻。)

  • shutil.which() 現在會查閱 PATHEXT 環境變數來查找 Windows 上 PATH 內的匹配項,即使給定的 cmd 包含目錄元件也是如此。(由 Charles Machalow 在 gh-103179 中貢獻。)

    在 Windows 上查詢可執行檔時,shutil.which() 將呼叫 NeedCurrentDirectoryForExePathW 以確定是否應將當前工作目錄新增到搜尋路徑前面。(由 Charles Machalow 在 gh-103179 中貢獻。)

    shutil.which() 將優先回傳 cmdPATHEXT 中元件相匹配的路徑,然後才在 Windows 搜尋路徑中的其他位置進行直接匹配。(由 Charles Machalow 在 gh-103179 中貢獻。)

sqlite3

statistics

sys

tempfile

threading

tkinter

  • tkinter.Canvas.coords() 現在將其參數展平。它現在不僅接受座標作為單獨的引數 (x1, y1, x2, y2, ...) 和一系列座標 ([x1, y1, x2, y2, ...]),而且還有成對分組的座標 ((x1, y1), (x2, y2), ...[(x1, y1), (x2, y2), ...]),如 create_*() 方法。(由 Serhiy Storchaka 在 gh-94473 中貢獻。)

tokenize

types