1. 程式人生 > 實用技巧 >47、python3.8的新特性

47、python3.8的新特性

python3.8的新增功能

本文介紹了與3.7相比,Python 3.8的新功能。有關完整的詳細資訊,請參見changelog

Python 3.8已於2019年10月14日釋出。

新功能

賦值表示式

有一種新語法:=可將值賦給變數,作為較大表達式的一部分。由於它與海象的眼睛和象牙很像,因此被親切地稱為“海象操作員” 。

在此示例中,賦值表示式有助於避免呼叫 len()兩次:

if (n := len(a)) > 10:
    print(f"List is too long ({n} elements, expected <= 10)")

在正則表示式匹配期間會產生類似的好處,其中需要兩次匹配物件,一次是測試是否發生匹配,另一次是提取子組:

discount = 0.0
if (mo := re.search(r'(\d+)% discount', advertisement)):
    discount = float(mo.group(1)) / 100.0

該運算子對while迴圈也很有用,該迴圈計算一個值以測試迴圈終止,然後在迴圈體中再次需要相同的值:

# Loop over fixed length blocks
while (block := f.read(256)) != '':
    process(block)

另一個具有啟發性的用例出現在列表理解中,其中表達式主體中還需要在過濾條件下計算出的值:

[clean_name.title() for name in names
 if (clean_name := normalize('NFC', name)) in allowed_names]

請儘量將海象運算子的使用限制在清晰的場合中,以降低複雜性並提升可讀性。

看到 有關完整說明,請參見PEP 572

僅限位置形參

新增了一個函式形參語法 / 用來指明某些函式形參必須使用僅限位置而非關鍵字引數的形式。 這種標記語法與通過 help() 所顯示的使用 Larry Hastings 的 Argument Clinic 工具標記的 C 函式相同。

在下面的例子中,形參 ab 為僅限位置形參,cd 可以是位置形參或關鍵字形參,而 ef 要求為關鍵字形參:

def f(a, b, /, c, d, *, e, f):
    print(a, b, c, d, e, f)

以下均為合法的呼叫:

f(10, 20, 30, d=40, e=50, f=60)

但是,以下均為不合法的呼叫:

f(10, b=20, c=30, d=40, e=50, f=60)   # b cannot be a keyword argument
f(10, 20, 30, 40, 50, f=60)           # e must be a keyword argument

這種標記形式的一個用例是它允許純 Python 函式完整模擬現有的用 C 程式碼編寫的函式的行為。 例如,內建的 pow() 函式不接受關鍵字引數:

def pow(x, y, z=None, /):
    "Emulate the built in pow() function"
    r = x ** y
    return r if z is None else r%z

另一個用例是在不需要形參名稱時排除關鍵字引數。 例如,內建的 len() 函式的簽名為 len(obj, /)。 這可以排除如下這種笨拙的呼叫形式:

len(obj='hello')  # The "obj" keyword argument impairs readability

另一個益處是將形參標記為僅限位置形參將允許在未來修改形參名而不會破壞客戶的程式碼。 例如,在 statistics 模組中,形參名 dist 在未來可能被修改。 這使得以下函式描述成為可能:

def quantiles(dist, /, *, n=4, method='exclusive')
    ...

由於在 / 左側的形參不會被公開為可用關鍵字,其他形參名仍可在 **kwargs 中使用:

>>> def f(a, b, /, **kwargs):
...     print(a, b, kwargs)
...
>>> f(10, 20, a=1, b=2, c=3)         # a and b are used in two ways
10 20 {'a': 1, 'b': 2, 'c': 3}

這極大地簡化了需要接受任意關鍵字引數的函式和方法的實現。 例如,以下是一段摘自 collections 模組的程式碼:

class Counter(dict):
    def __init__(self, iterable=None, /, **kwds):
        # Note "iterable" is a possible keyword argument

請參閱 PEP 570 瞭解詳情。

用於已編譯位元組碼檔案的並行檔案系統快取

新增的 PYTHONPYCACHEPREFIX 設定 (也可使用 -X pycache_prefix) 可將隱式的位元組碼快取配置為使用單獨的並行檔案系統樹,而不是預設的每個原始碼目錄下的 __pycache__ 子目錄。

快取的位置會在 sys.pycache_prefix 中報告 (None 表示預設位置即 __pycache__ 子目錄)。

除錯構建使用與釋出構建相同的 ABI

Python 現在不論是以釋出模式還是除錯模式進行構建都將使用相同的 ABI。 在 Unix 上,當 Python 以除錯模式構建時,現在將可以載入以釋出模式構建的 C 擴充套件和使用穩定版 ABI 構建的 C 擴充套件。

釋出構建和除錯構建現在都是 ABI 相容的:定義 Py_DEBUG 巨集不會再啟用 Py_TRACE_REFS 巨集,它引入了唯一的 ABI 不相容性。 Py_TRACE_REFS 巨集添加了 sys.getobjects() 函式和 PYTHONDUMPREFS 環境變數,它可以使用新的 ./configure --with-trace-refs 構建選項來設定。 (由 Victor Stinner 在 bpo-36465 中貢獻。)

在 Unix 上,C 擴充套件不會再被連結到 libpython,但 Android 和 Cygwin 例外。 現在靜態連結的 Python 將可以載入使用共享庫 Python 構建的 C 擴充套件。 (由 Victor Stinner 在 bpo-21536 中貢獻。)

在 Unix 上,當 Python 以除錯模式構建時,匯入操作現在也會查詢在釋出模式下編譯的 C 擴充套件以及使用穩定版 ABI 編譯的 C 擴充套件。 (由 Victor Stinner 在 bpo-36722 中貢獻。)

要將 Python 嵌入到一個應用中,必須將新增的 --embed 選項傳給 python3-config --libs --embed 以獲得 -lpython3.8 (將應用連結到 libpython)。 要同時支援 3.8 和舊版本,請先嚐試 python3-config --libs --embed 並在此命令失敗時回退到 python3-config --libs (即不帶 --embed)。

增加一個 pkg-config python-3.8-embed 模組用來將 Python 嵌入到一個應用中: pkg-config python-3.8-embed --libs 包含 -lpython3.8。 要同時支援 3.8 和舊版本,請先嚐試 pkg-config python-X.Y-embed --libs 並在此命令失敗時回退到 pkg-config python-X.Y --libs (即不帶 --embed) (請將 X.Y 替換為 Python 版本號)。

另一方面,pkg-config python3.8 --libs 不再包含 -lpython3.8。 C 擴充套件不可被連結到 libpython

f-字串支援 = 用於自動記錄表示式和除錯文件

增加 = 說明符用於 f-string。 形式為 f'{expr=}' 的 f-字串將擴充套件表示為表示式文字,加一個等於號,再加表示式的求值結果。 例如:

>>> user = 'eric_idle'
>>> member_since = date(1975, 7, 31)
>>> f'{user=} {member_since=}'
"user='eric_idle' member_since=datetime.date(1975, 7, 31)"

通常的 f-字串格式說明符 允許更細緻地控制所要顯示的表示式結果:

>>> delta = date.today() - member_since
>>> f'{user=!s}  {delta.days=:,d}'
'user=eric_idle  delta.days=16,075'

= 說明符將輸出整個表示式,以便詳細演示計算過程:

>>> print(f'{theta=}  {cos(radians(theta))=:.3f}')
theta=30  cos(radians(theta))=0.866

新增模組

新增的 importlib.metadata 模組提供了從第三方包讀取元資料的(臨時)支援。 例如,它可以提取一個已安裝軟體包的版本號、入口點列表等等:

>>> # Note following example requires that the popular "requests"
>>> # package has been installed.
>>>
>>> from importlib.metadata import version, requires, files
>>> version('requests')
'2.22.0'
>>> list(requires('requests'))
['chardet (<3.1.0,>=3.0.2)']
>>> list(files('requests'))[:5]
[PackagePath('requests-2.22.0.dist-info/INSTALLER'),
 PackagePath('requests-2.22.0.dist-info/LICENSE'),
 PackagePath('requests-2.22.0.dist-info/METADATA'),
 PackagePath('requests-2.22.0.dist-info/RECORD'),
 PackagePath('requests-2.22.0.dist-info/WHEEL')]

改進的模組

ast

AST 節點現在具有 end_linenoend_col_offset 屬性,它們給出節點結束的精確位置。 (這隻適用於具有 linenocol_offset 屬性的節點。)

新增函式 ast.get_source_segment() 返回指定 AST 節點的原始碼。

ast.parse() 函式具有一些新的旗標:

  • type_comments=True 導致其返回與特定 AST 節點相關聯的 PEP 484PEP 526 型別註釋文字;
  • mode='func_type' 可被用於解析 PEP 484 "簽名型別註釋" (為函式定義 AST 節點而返回);
  • feature_version=(3, N) 允許指定一個更早的 Python 3 版本。 (例如,feature_version=(3, 4) 將把 asyncawait 視為非保留字。)

asyncio

輸入python -m asyncio將直接發起非同步PEPL,這樣就不需要讓特別緊急的任務處於等待狀態,也不需要再輸入asyncio.run()來直接呼叫

$ python -m asyncio
asyncio REPL 3.8.0
Use "await" directly instead of "asyncio.run()".
Type "help", "copyright", "credits" or "license" for more information.
>>> import asyncio
>>> await asyncio.sleep(10, result='hello')
hello

在 Windows 上,現在預設的事件迴圈為 ProactorEventLoopProactorEventLoop 現在也支援 UDP。 ProactorEventLoop 現在可通過 KeyboardInterrupt("CTRL+C") 來中斷。

collections

collections.namedtuple()_asdict() 方法現在將返回 dict 而不是 collections.OrderedDict。 此項更改是因為普通字典自 Python 3.7 起已保證具有確定的元素順序。 如果還需要 OrderedDict 的額外特性,推薦的解決方案是將結果轉換為需要的型別: OrderedDict(nt._asdict())

curses

添加了一個新變數用於儲存下層 ncurses 庫的結構版資訊: ncurses_version

datetime

添加了新的替代構造器 datetime.date.fromisocalendar()datetime.datetime.fromisocalendar(),它們分別基於 ISO 年份、周序號和周內日序號來構造 datedatetime 物件;這兩者分別是其所對應類中 isocalendar 方法的逆操作。 (由 Paul Ganssle 在 bpo-36004 中貢獻。)

functools

functools.lru_cache() 現在可直接作為裝飾器而不是作為返回裝飾器的函式。 因此這兩種寫法現在都被支援:

@lru_cache
def f(x):
    ...

@lru_cache(maxsize=256)
def f(x):
    ...

添加了一個新的functools.cached_property()裝飾器,用於在例項生命期內快取的計算屬性。

import functools
import statistics

class Dataset:
   def __init__(self, sequence_of_numbers):
      self.data = sequence_of_numbers

   @functools.cached_property
   def variance(self):
      return statistics.variance(self.data)

gc

get_objects()現在可以接收一個可選的生成引數,指示從中獲取物件的生成

gettext

添加了 pgettext() 及其變化形式

gzip

新增 mtime 形參到 gzip.compress() 用於可重現的輸出。 (由 Guo Ci Teo 在 bpo-34898 中貢獻。)

對於特定型別的無效或已損壞 gzip 檔案現在將引發 BadGzipFile 而不是 OSError

idlelib 與 IDLE

超過 N 行(預設值為 50)的輸出將被摺疊為一個按鈕。 N 可以在 Settings 對話方塊的 General 頁的 PyShell 部分中進行修改。 數量較少但是超長的行可以通過在輸出上右擊來摺疊。 被摺疊的輸出可通過雙擊按鈕來展開,或是通過右擊按鈕來放入剪貼簿或是單獨的視窗。

在 Run 選單中增加了 "Run Customized" 以使用自定義設定來執行模組。 輸入的任何命令列引數都會被加入 sys.argv。 它們在下次自定義執行時會再次顯示在窗體中。 使用者也可以禁用通常的 Shell 主模組重啟。

在 IDLE 編輯器視窗中增加了可選的行序號。 視窗開啟時預設不帶行序號,除非在配置對話方塊的 General 選項卡中進行設定。 已開啟視窗中的行序號可以在 Options 選單中顯示和隱藏。

上述修改已被反向移植到 3.7 維護髮行版中。

inspect

inspect.getdoc() 函式現在可以找到 __slots__ 的文件字串,如果該屬性是一個元素值為文件字串的 dict 的話。 這提供了類似於目前已有的 property(), classmethod()staticmethod() 等函式的文件選項:

class AudioClip:
    __slots__ = {'bit_rate': 'expressed in kilohertz to one decimal place',
                 'duration': 'in seconds, rounded up to an integer'}
    def __init__(self, bit_rate, duration):
        self.bit_rate = round(bit_rate / 1000.0, 1)
        self.duration = ceil(duration)

io

在開發模式 (-X env) 和除錯構建中,io.IOBase 終結器現在會在 close() 方法失敗時將異常寫入日誌。 發生的異常在釋出構建中預設會被靜默忽略。

json.tool

新增選項 --json-lines 用於將每個輸入行解析為單獨的 JSON 物件。

math

添加了新的函式 math.dist() 用於計算兩點之間的歐幾里得距離。

擴充套件了 math.hypot() 函式以便處理更多的維度。 之前它僅支援 2-D 的情況。

添加了新的函式 math.prod() 作為的 sum() 同類,該函式返回 'start' 值 (預設值: 1) 乘以一個數字可迭代物件的積:

>>> prior = 0.8
>>> likelihoods = [0.625, 0.84, 0.30]
>>> math.prod(likelihoods, start=prior)
0.126

添加了新的函式 math.isqrt() 用於計算整數平方根。

函式 math.factorial() 不再接受非整數類引數。

mmap

mmap.mmap 類現在具有一個 madvise() 方法用於訪問 madvise() 系統呼叫。

multiprocessing

新增 multiprocessing.shared_memory 模組.

在macOS上,現在預設使用的啟動方式是spawn啟動方式。

os

添加了新的 os.memfd_create() 函式用於包裝 memfd_create() 系統呼叫。

在 Windows 上,大部分用於處理重解析點,(包括符號連結和目錄連線)的手動邏輯已被委託給作業系統。 特別地,os.stat() 現在將會遍歷作業系統所支援的任何內容,而 os.lstat() 將只打開被標識為“名稱代理”的重解析點,而其要由 os.stat() 開啟其他的重解析點。 在所有情況下,stat_result.st_mode 將只為符號連結而非其他種類的重解析點設定 S_IFLNK。 要標識其他種類的重解析點,請檢查新的 stat_result.st_reparse_tag 屬性。

在 Windows 上,os.readlink() 現在能夠讀取目錄連線。 請注意 islink() 會對目錄連線返回 False,因此首先檢查 islink 的程式碼將連續把連線視為目錄,而會處理 os.readlink() 所引發錯誤的程式碼現在會把連線視為連結。

os.path

返回布林值結果的 os.path 函式例如 exists(), lexists(), isdir(), isfile(), islink(), 以及 ismount() 現在對於包含在 OS 層級無法表示的字元或位元組的路徑將會返回 False 而不是引發 ValueError 或其子類 UnicodeEncodeErrorUnicodeDecodeError

expanduser() on Windows now prefers the USERPROFILE environment variable and does not use HOME, which is not normally set for regular user accounts.

isdir() 在 Windows 上不再為不存在的目錄的連結返回真值。

realpath() 在 Windows 上現在會識別重解析點,包括符號連結和目錄連線。

pathlib

返回布林值結果的 pathlib.Path 方法例如 exists(), is_dir(), is_file(), is_mount(), is_symlink(), is_block_device(), is_char_device(), is_fifo(), is_socket() 現在對於包含在 OS 層級無法表示的字元或位元組的路徑將會返回 False 而不是引發 ValueError 或其子類 UnicodeEncodeError

添加了 pathlib.Path.link_to() 用於建立指向某個路徑的硬連結。

plistlib

添加了新的 plistlib.UID 並啟動了對讀取和寫入經過 NSKeyedArchiver 編碼的二進位制 plists 的支援

py_compile

py_compile.compile() 現在支援靜默模式。

shlex

新增了 shlex.join() 函式作為 shlex.split() 的逆操作。

shutil

shutil.copytree() 現在接受新的 dirs_exist_ok 關鍵字引數。

shutil.make_archive() 現在對新的歸檔預設使用 modern pax (POSIX.1-2001) 格式以提升可移植性和標準一致性,此特性繼承自對 tarfile 模組的相應更改。

shutil.rmtree() on Windows now removes directory junctions without recursively removing their contents first.

socket

添加了便捷的 create_server()has_dualstack_ipv6() 函式以自動化在建立伺服器套接字時通常情況下所必須的任務,包括在同一套接字中同時接受 IPv4 和 IPv6 連線。

socket.if_nameindex(), socket.if_nametoindex()socket.if_indextoname() 函式已經在 Windows 上實現。

ssl

增加了 ssl.SSLContext.post_handshake_auth 以及 ssl.SSLSocket.verify_client_post_handshake() 來啟用並初始化 TLS 1.3 握手後驗證。

statistics

添加了 statistics.fmean() 作為 statistics.mean() 的更快速的浮點數版版本。

添加了 statistics.geometric_mean()

添加了 statistics.multimode() 用於返回最常見值的列表。

添加了 statistics.quantiles() 用於將資料或分佈劃分為多個等概率區間

添加了 statistics.NormalDist 用於建立和操縱隨機變數的正態分佈。

>>> temperature_feb = NormalDist.from_samples([4, 12, -3, 2, 7, 14])
>>> temperature_feb.mean
6.0
>>> temperature_feb.stdev
6.356099432828281

>>> temperature_feb.cdf(3)            # Chance of being under 3 degrees
0.3184678262814532
>>> # Relative chance of being 7 degrees versus 10 degrees
>>> temperature_feb.pdf(7) / temperature_feb.pdf(10)
1.2039930378537762

>>> el_niño = NormalDist(4, 2.5)
>>> temperature_feb += el_niño        # Add in a climate effect
>>> temperature_feb
NormalDist(mu=10.0, sigma=6.830080526611674)

>>> temperature_feb * (9/5) + 32      # Convert to Fahrenheit
NormalDist(mu=50.0, sigma=12.294144947901014)
>>> temperature_feb.samples(3)        # Generate random samples
[7.672102882379219, 12.000027119750287, 4.647488369766392]

sys

添加了新的 sys.unraisablehook() 函式,可被過載以便控制如何處理“不可引發的異常”。 它會在發生了一個異常但 Python 沒有辦法處理時被呼叫。 例如,當一個析構器在垃圾回收時 (gc.collect()) 所引發的異常。

tarfile

tarfile 模組現在對新的歸檔預設使用 modern pax (POSIX.1-2001) 格式而不再是之前的 GNU 專屬格式。 這通過標準化和可擴充套件格式的統一編碼 (UTF-8) 提升了跨平臺可移植性,還提供了其他一些益處。

threading

添加了新的 threading.excepthook() 函式用來處理未捕獲的 threading.Thread.run() 異常。 它可被過載以便控制如何處理未捕獲的 threading.Thread.run() 異常。

添加了新的 threading.get_native_id() 函式以及 threading.Thread 類的 native_id 屬性。 它們會返回核心所分配給當前執行緒的原生整數執行緒 ID。 此特性僅在特定平臺上可用,參見 get_native_id 瞭解詳情。

tokenize

當提供不帶末尾新行的輸入時,tokenize 模組現在會隱式地新增 NEWLINE 形符。 此行為現在已與 C 詞法分析器的內部行為相匹配。

tkinter

tkinter.Spinbox 中添加了方法 selection_from(), selection_present(), selection_range()selection_to()

tkinter.Canvas 類中添加了方法 moveto()

tkinter.PhotoImage 類現在具有 transparency_get()transparency_set() 方法。

time

為 macOS 10.12 添加了新的時鐘 CLOCK_UPTIME_RAW

typing

typing 模組加入了一些新特性:

  • 一個帶有鍵專屬型別的字典型別。 參見 PEP 589typing.TypedDict。 TypedDict 只使用字串作為鍵。 預設情況下每個鍵都要求提供。 指定 "total=False" 以允許鍵作為可選項:

    class Location(TypedDict, total=False):
        lat_long: tuple
        grid_square: str
        xy_coordinate: tuple
    
  • Literal 型別。 參見 PEP 586typing.Literal。 Literal 型別指明一個形參或返回值被限定為一個或多個特定的字面值:

    def get_status(port: int) -> Literal['connected', 'disconnected']:
        ...
    
  • "Final" 變數、函式、方法和類。 參見 PEP 591, typing.Finaltyping.final()。 final 限定符會指示靜態型別檢查器限制進行子類化、過載或重新賦值:

    pi: Final[float] = 3.1415926536
    
  • 協議定義。 參見 PEP 544, typing.Protocoltyping.runtime_checkable()。 簡單的 ABC 例如 typing.SupportsInt 現在是 Protocol 的子類。

  • 新的協議類 typing.SupportsIndex

  • 新的函式 typing.get_origin()typing.get_args()

unicodedata

unicodedata 模組現在已升級為使用 Unicode 12.1.0 釋出版。

新的函式 is_normalized() 可被用來驗證字串是否為特定正規形式,通常會比實際進行字串正規化要快得多。

unittest

添加了 AsyncMock 以支援非同步版本的 Mock。 同時也添加了相應的斷言函式用於測試。

unittest 添加了 addModuleCleanup()addClassCleanup() 以支援對 setUpModule()setUpClass() 進行清理。

一些模擬斷言函式現在也會在失敗時列印一個實際呼叫列表。

unittest 模組已支援通過 unittest.IsolatedAsyncioTestCase 來使用協程作為測試用例。

import unittest


class TestRequest(unittest.IsolatedAsyncioTestCase):

    async def asyncSetUp(self):
        self.connection = await AsyncConnection()

    async def test_get(self):
        response = await self.connection.get("https://example.com")
        self.assertEqual(response.status_code, 200)

    async def asyncTearDown(self):
        await self.connection.close()


if __name__ == "__main__":
    unittest.main()

venv

現在 venv 在所有平臺上都會包含 Activate.ps1 指令碼用於在 PowerShell Core 6.1 下啟用虛擬環境。

weakref

weakref.proxy() 返回的代理物件現在除其他算術運算子外也支援矩陣乘法運算子 @@=

xml

作為對 DTD 和外部實體檢索的緩解,在預設情況下 xml.dom.minidomxml.sax 模組不再處理外部實體。

xml.etree.ElementTree 模組中的 .find*() 方法支援萬用字元搜尋例如 {*}tag,此搜尋會忽略名稱空間以及返回給定名稱空間中所有標籤的 {namespace}*

xml.etree.ElementTree 模組提供了實現 C14N 2.0 的新函式 –xml.etree.ElementTree.canonicalize()

xml.etree.ElementTree.XMLParser 的目標物件可通過新的回撥方法 start_ns()end_ns() 來接受名稱空間宣告事件。 此外,xml.etree.ElementTree.TreeBuilder 目標可被配置為處理有關注釋和處理指令事件以將它們包含在所生成的樹當中。

效能優化

  • subprocess 模組現在能在某些情況下使用 os.posix_spawn() 函式以獲得更好的效能。 目前,它的使用僅限 macOS 和 Linux(使用 glibc 2.24 或更新版本),並要求滿足以下條件:

    • close_fds 為假值;
    • preexec_fn, pass_fds, cwdstart_new_session 形參未設定;
    • executable 路徑包含一個目錄。

    (由 Joannah Nanjekye 和 Victor Stinner 在 bpo-35537 中貢獻。)

  • shutil.copyfile(), shutil.copy(), shutil.copy2(), shutil.copytree()shutil.move() 在 Linux 和 macOS 上會使用平臺專屬的 "fast-copy" 系統呼叫以提高效率。 "fast-copy" 意味著拷貝操作發生於核心中,從而避免在進行 "outfd.write(infd.read())" 等操作時使用 Python 中的使用者空間緩衝區。 在 Windows 上 shutil.copyfile() 會使用更大的預設緩衝區(1 MiB 而不是 16 KiB)並且使用基於 memoryview()shutil.copyfileobj() 版本。 在同一分割槽內拷貝一個 512 MiB 檔案的速度提升在 Linux 上約為 +26%,在 macOS 上為 +50%,在 Windows 上為 +40%。 此外還將消耗更少的 CPU 週期。 參見 Platform-dependent efficient copy operations 一節。 (由 Giampaolo Rodolà 在 bpo-33671 中貢獻。)

  • shutil.copytree() 會根據其所用快取的 os.stat() 值使用 os.scandir() 函式及所有拷貝函式。 拷貝一個包含 8000 檔案的目錄的速度提升在 Linux 上約為 +9%,在 Windows 上為 +20%,對於 Windows SMB 共享目錄則為 +30%。 此外 os.stat() 系統呼叫的次數也減少了 38%,使得 shutil.copytree() 在網路檔案系統上會特別快速。 (由Giampaolo Rodolà 在 bpo-33695 中貢獻。)

  • pickle 模組使用的預設協議現在為 Protocol 4,最早在 Python 3.4 中被引入。 它提供了比自 Python 3.0 起可用的 Protocol 3 更好的效能和更小的資料尺寸。

  • Removed one Py_ssize_t member from PyGC_Head. All GC tracked objects (e.g. tuple, list, dict) size is reduced 4 or 8 bytes. (Contributed by Inada Naoki in bpo-33597.)

  • uuid.UUID now uses __slots__ to reduce its memory footprint. (Contributed by Wouter Bolsterlee and Tal Einat in bpo-30977)

  • operator.itemgetter() 的效能提升了 33%。 優化了引數處理,併為常見的在元組中單個非負整數索引的情況新增了一條快速路徑(這是標準庫中的典型用例)。 (由 Raymond Hettinger 在 bpo-35664 中貢獻。

  • 加快了在 collections.namedtuple() 中的欄位查詢。 它們現在的速度快了兩倍以上,成為 Python 中最快的例項變數查詢形式。 (由 Raymond Hettinger, Pablo Galindo 和 Joe Jevnik, Serhiy Storchaka 在 bpo-32492 中貢獻。)

  • 如果輸入的可迭代物件的長度已知 (即輸入物件實現了 __len__),list 構造器不會過度分配內部項緩衝區。 這使得所建立的列表資源佔用平均減少了 12%。 (由 Raymond Hettinger 和 Pablo Galindo 在 bpo-33234 中貢獻。)

  • 類變數寫入速度加倍。 當一個非冗餘屬性被更新時,之前存在一個更新 slots 的非必要呼叫。 (由 Stefan Behnel, Pablo Galindo Salgado, Raymond Hettinger, Neil Schemenauer, 和 Serhiy Storchaka 在 bpo-36012 中貢獻。)

  • 減少了傳遞給許多內建函式和方法的引數轉換的開銷。 這使得某些簡單內建函式和方法的速度提升了 20--50%。 (由 Serhiy Storchaka 在 bpo-23867, bpo-35582bpo-36127 中貢獻。)

  • LOAD_GLOBAL 指令現在會使用新的 "per opcode cache" 機制。 它的速度現在提升了大約 40%。 (由 Yury Selivanov 和 Inada Naoki 在 bpo-26219 中貢獻。)

構建和 C API 的改變

  • 預設的 sys.abiflags 成為一個空字串:pymalloc 的 m 旗標不再有意義(無論是否啟用 pymalloc 構建都是相容 ABI 的)因此已被移除。 (由 Victor Stinner 在 bpo-36707 中貢獻。)

    改變的例子:

    • 只會安裝 python3.8 程式,不再有 python3.8m 程式。
    • 只會安裝 python3.8-config 指令碼,不再有 python3.8m-config 指令碼。
    • m 旗標已經從動態庫檔名字尾中移除:包括標準庫中的擴充套件模組以及第三方包所產生和安裝的模組例如從 PyPI 下載的模組。 以 Linux 為例,Python 3.7 的字尾 .cpython-37m-x86_64-linux-gnu.so 在 Python 3.8 中改為 .cpython-38-x86_64-linux-gnu.so
  • 重新組織了所有標頭檔案以更好地區分不同種類的 API:

    • Include/*.h 應為可移植的公有穩定版 C API。
    • Include/cpython/*.h 應為 CPython 專屬的不穩定版 C API;公有 API,部分私有 API 附加 _Py or _PY 字首。
    • Include/internal/*.h 應為 CPython 特別專屬的私有內部 C API。 此 API 不具備向下相容保證並且不應在 CPython 以外被使用。 它們的公開僅適用於特別限定的需求例如偵錯程式和效能分析等必須直接訪問 CPython 內部資料而不通過呼叫函式的應用。 此 API 現在是通過 make install 安裝的。

    (由 Victor Stinner 在 bpo-35134bpo-35081 中貢獻,相關工作由 Eric Snow 在 Python 3.7 中發起。)

  • 某些巨集已被轉換為靜態行內函數:形參型別和返回型別定義良好,它們不再會有與巨集相關的問題,變數具有區域性作用域。 例如:

    (由 Victor Stinner 在 bpo-35059 中貢獻。)

  • PyByteArray_Init()PyByteArray_Fini() 函式已被移除。 它們自 Python 2.7.4 和 Python 3.2.0 起就沒有任何用處,被排除在受限 API (穩定版 ABI) 之外,並且未被寫入文件。 (由 Victor Stinner 在 bpo-35713 中貢獻。)

  • PyExceptionClass_Name() 的結果型別現在是 const char * 而非 char *。 (由 Serhiy Storchaka 在 bpo-33818 中貢獻。)

  • Modules/Setup.distModules/Setup 兩者的共存已被移除。 之前在更新 CPython 原始碼樹時,開發者必須手動拷貝 Modules/Setup.dist (在原始碼樹內) 到 Modules/Setup (在構建樹內) 以反映上游的任何改變。 舊特性對打包者來說有一點益處,但代價是對追蹤 CPython 開發程序的開發者造成經常性的麻煩,因為忘記拷貝該檔案可能導致構建失敗。

    現在構建系統總是會從原始碼樹內的 Modules/Setup 讀取資料。 建議希望定製該檔案的開發者在 CPython 的一個 git 分叉或補丁檔案中維護他們的更改,就如他們對原始碼樹做任何其他改變時一樣。

    (由 Antoine Pitrou 在 bpo-32430 中貢獻。)

  • 將 Python 數字轉換為 C 整型的函式例如 PyLong_AsLong() 以及帶有 'i' 之類整型轉換格式單元的引數解析函式例如 PyArg_ParseTuple() 現在如果可能將會使用 __index__() 特殊方法而不是 __int__()。 對於帶有 __int__() 方法但沒有 __index__() 方法的物件 (例如 DecimalFraction) 將會發出棄用警告。 對於實現了 __index__() 的物件 PyNumber_Check() 現在將返回 1PyNumber_Long(), PyNumber_Float()PyFloat_AsDouble() 現在如果可能也將會使用 __index__() 方法。 (由 Serhiy Storchaka 在 bpo-36048bpo-20092 中貢獻。)

  • 堆分配型別物件現在將增加它們在 PyObject_Init() (及其對應的巨集 PyObject_INIT) 中的引用計數而不是在 PyType_GenericAlloc() 中。 修改例項分配或中止分配的型別可能需要進行調整。 (由 Elizondo 在 bpo-35810 中貢獻。)

  • 新增函式 PyCode_NewWithPosOnlyArgs() 允許建立程式碼物件例如 PyCode_New(),但帶有一個額外的 posonlyargcount 形參以指明僅限位置引數的數量。 (由 Pablo Galindo 在 bpo-37221 中貢獻。)

  • Py_SetPath() 現在會將 sys.executable 設為程式完整路徑 (Py_GetProgramFullPath()) 而不是程式名稱 (Py_GetProgramName())。 (由 Victor Stinner 在 bpo-38234 中貢獻。)

棄用

API 與特性的移除

下列特性與 API 已從 Python 3.8 中移除:

  • macpath 模組,在 Python 3.7 中棄用,現已被移除。
  • 函式 platform.popen() 已被移除,它自 Python 3.3 起就已被棄用:請改用 os.popen()
  • 函式 time.clock() 已被移除,它自 Python 3.3 起就已被棄用:請根據具體需求改用 time.perf_counter()time.process_time() 以獲得具有良好定義的行為。
  • pyvenv 指令碼已被移除,推薦改用 python3.8 -m venv 來幫助消除容易混淆 pyvenv 指令碼所關聯的 Python 直譯器這一問題。
  • parse_qs, parse_qslescape 已從 cgi 模組中被移除。 這些函式自 Python 3.2 或更早就已被棄用。 它們應改為從 urllib.parsehtml 模組匯入。
  • filemode 函式已從 tarfile 模組中被移除。 該函式未被寫入文件,自 Python 3.3 起就已棄用。
  • XMLParser 構造器不再接受 html 引數。 它從來沒有任何作用並在 Python 3.4 中已被棄用。 所有其他形參現在都是 僅限關鍵字引數
  • XMLParserdoctype() 方法已被移除。
  • "unicode_internal" 編解碼器已被移除。
  • sqlite3 模組的 CacheStatement 物件已不再公開給使用者。
  • fileinput.input()fileinput.FileInput() 中自 Python 3.6 起就已被忽略並棄用的 bufsize 關鍵字引數已被移除。
  • 在 Python 3.7 中棄用的函式 sys.set_coroutine_wrapper()sys.get_coroutine_wrapper() 已被移除。

移植到 Python 3.8

本節列出了先前描述的更改以及可能需要更改程式碼的其他錯誤修正.

Python 行為的改變

  • yield 表示式(包括 yieldyield from 子句)現在不允許在推導式和生成器表示式中使用(但 for 子句最左邊的可迭代物件表示式除外)。 (由 Serhiy Storchaka 在 bpo-10544 中貢獻。)
  • The compiler now produces a SyntaxWarning when identity checks (is and is not) are used with certain types of literals (e.g. strings, numbers). These can often work by accident in CPython, but are not guaranteed by the language spec. The warning advises users to use equality tests (== and !=) instead. (Contributed by Serhiy Storchaka in bpo-34850.)
  • CPython 直譯器在某些情況下可以忽略異常。 在 Python 3.8 中這種情況會更少發生。 特別地,從型別字典獲取屬性時引發的異常不會再被忽略。 (由 Serhiy Storchaka 在 bpo-35459 中貢獻。)
  • 從內建型別 bool, int, float, complex 和標準庫的一些類中移除了 __str__ 實現。 它們現在會從 object 繼承 __str__()。 作為結果,在這些類的子類中定義 __repr__() 方法將會影響它們的字串表示。 (由 Serhiy Storchaka 在 bpo-36793 中貢獻。)
  • 在 AIX 上,sys.platform 將不再包含主要版本號。 它將總是 'aix' 而不再是 'aix3' .. 'aix7'。 由於較舊的 Python 版本會包含該版本號,因此推薦總是使用 sys.platform.startswith('aix')。 (由 M. Felt 在 bpo-36588 中貢獻。)
  • PyEval_AcquireLock() and PyEval_AcquireThread() now terminate the current thread if called while the interpreter is finalizing, making them consistent with PyEval_RestoreThread(), Py_END_ALLOW_THREADS(), and PyGILState_Ensure(). If this behavior is not desired, guard the call by checking _Py_IsFinalizing() or sys.is_finalizing(). (Contributed by Joannah Nanjekye in bpo-36475.)

更改的Python API

  • 在 Windows 上 os.getcwdb() 函式現在會使用 UTF-8 編碼格式而不是 ANSI 內碼表:請參看 PEP 529 瞭解具體原因。 該函式在 Windows 上不再被棄用。 (由 Victor Stinner 在 bpo-37412 中貢獻。)
  • 現在 subprocess.Popen 在某些情況下會使用 os.posix_spawn() 以獲得更好的效能。 在適用於 Linux 的 Windows 子系統和 QEMU 使用者模擬器上,使用 os.posix_spawn()Popen 構造器不會再因為“找不到程式”這樣的錯誤引發異常。 而是讓子程序失敗並返回一個非零的 returncode。 (由 Joannah Nanjekye 和 Victor Stinner 在 bpo-35537 中貢獻。)
  • The preexec_fn argument of * subprocess.Popen is no longer compatible with subinterpreters. The use of the parameter in a subinterpreter now raises RuntimeError. (Contributed by Eric Snow in bpo-34651, modified by Christian Heimes in bpo-37951.)
  • The imap.IMAP4.logout() method no longer ignores silently arbitrary exceptions. (Contributed by Victor Stinner in bpo-36348.)
  • 函式 platform.popen() 已被移除,它自 Python 3.3 起就已被棄用:請改用 os.popen()。 (由 Victor Stinner 在 bpo-35345 中貢獻。)
  • 當傳入多模資料時 statistics.mode() 函式不會再引發異常。 它將改為返回在輸入資料中遇到的第一個模式。 (由 Raymond Hettinger 在 bpo-35892 中貢獻。)
  • tkinter.ttk.Treeview 類的 selection() 方法不再接受引數。 帶引數呼叫該方法來改變選擇在 Python 3.6 中已棄用。 請使用專門方法例如 selection_set() 來改變選擇。 (由 Serhiy Storchaka 在 bpo-31508 中貢獻。)
  • xml.dom.minidomwritexml(), toxml()toprettyxml() 方法以及 xml.etreewrite() 方法現在會保留使用者指定的屬性順序。 (由 Diego Rojas 和 Raymond Hettinger 在 bpo-34160 中貢獻。)
  • 附帶 'r' 旗標開啟的 dbm.dumb 資料庫現在將是隻讀的。 如果資料庫不存在,附帶 'r''w' 旗標的 dbm.dumb.open() 不會再建立資料庫。 (由 Serhiy Storchaka 在 bpo-32749 中貢獻。)
  • XMLParser 的子類中定義的 doctype() 方法將不會再被呼叫,並將導致發出 RuntimeWarning 而不是 DeprecationWarning。 請在目標上定義 doctype() 方法來處理 XML doctype 宣告。 (由 Serhiy Storchaka 在 bpo-29209 中貢獻。)
  • 現在當自定義元類未在傳給 type.__new__ 的名稱空間中提供 __classcell__ 入口時將引發 RuntimeError。 在 Python 3.6--3.7 中是則是引發 DeprecationWarning。 (由 Serhiy Storchaka 在 bpo-23722 中貢獻。)
  • cProfile.Profile 類現在可被用作上下文管理器。 (由 Scott Sanderson 在 bpo-29235 中貢獻。)
  • shutil.copyfile(), shutil.copy(), shutil.copy2(), shutil.copytree()shutil.move() 會使用平臺專屬的 "fast-copy" 系統呼叫(參見 Platform-dependent efficient copy operations 一節)。
  • shutil.copyfile() 在 Windows 上的預設緩衝區大小從 16 KiB 改為 1 MiB。
  • PyGC_Head 結構已被完全改變。 所有接觸到該結構的程式碼都應當被重寫。 (參見 bpo-33597。)
  • PyInterpreterState 結構已被移入 "internal" 標頭檔案 (特別是 Include/internal/pycore_pystate.h)。 不透明的 PyInterpreterState 作為公有 API (和穩定版 ABI) 的一部分仍然可用。 文件指明該結構的任何欄位都不是公有的,因此我們希望沒人在使用它們。 但是,如果你確實依賴其中某一個或更多個私有欄位並且沒有其他替代選項,則請開一個 BPO 問題。 我們將盡力幫助你進行調整 (可能包括向公有 API 新增訪問器函式)。 (參見 bpo-35886。)
  • 現在 asyncio 任務可以被命名,具體方式是將 name 關鍵字引數傳給 asyncio.create_task()create_task() 事件迴圈方法,或是在任務物件上呼叫 set_name() 方法。 任務名稱可在 asyncio.Taskrepr() 輸出中檢視,並可使用 get_name() 方法來獲取。
  • 現在所有平臺下的 mmap.flush() 方法都會在成功時返回 None 並在錯誤時引發異常。 之前它的行為取決於具體平臺: Windows 下會在成功時返回非零值;在失敗時返回零。 Unix 下會在成功時返回零;在失敗時引發錯誤。 (由 Berker Peksag 在 bpo-2122 中貢獻。)
  • xml.dom.minidomxml.sax 模組預設將不再處理外部實體。 (由 Christian Heimes 在 bpo-17239 中貢獻。)
  • 從只讀的 dbm 資料庫 (dbm.dumb, dbm.gnudbm.ndbm) 刪除鍵將會引發 error (dbm.dumb.error, dbm.gnu.errordbm.ndbm.error) 而不是 KeyError。 (由 Xiang Zhang 在 bpo-33106 中貢獻。)
  • expanduser() on Windows now prefers the USERPROFILE environment variable and does not use HOME, which is not normally set for regular user accounts. (Contributed by Anthony Sottile in bpo-36264.)
  • The exception asyncio.CancelledError now inherits from BaseException rather than a Exception. (Contributed by Yury Selivanov in bpo-13528.)
  • DLL dependencies for extension modules and DLLs loaded with ctypes on Windows are now resolved more securely. Only the system paths, the directory containing the DLL or PYD file, and directories added with add_dll_directory() are searched for load-time dependencies. Specifically, PATH and the current working directory are no longer used, and modifications to these will no longer have any effect on normal DLL resolution. If your application relies on these mechanisms, you should check for add_dll_directory() and if it exists, use it to add your DLLs directory while loading your library. Note that Windows 7 users will need to ensure that Windows Update KB2533625 has been installed (this is also verified by the installer). (Contributed by Steve Dower in bpo-36085.)
  • 關聯到 pgen 的標頭檔案和函式在其被純 Python 實現取代後已被移除。 (由 Pablo Galindo 在 bpo-36623 中貢獻。)
  • types.CodeType 在構造器的第二個位置新增了一個形參 (posonlyargcount) 以支援在 PEP 570 中定義的僅限位置引數。 第一個引數 (argcount) 現在表示位置引數的總數量 (包括僅限位置引數)。 types.CodeType 中新增的 replace() 方法可用於讓程式碼支援 future 特性。

C API 中的改變

  • The PyCompilerFlags structure got a new cf_feature_version field. It should be initialized to PY_MINOR_VERSION. The field is ignored by default, and is used if and only if PyCF_ONLY_AST flag is set in cf_flags. (Contributed by Guido van Rossum in bpo-35766.)

  • PyEval_ReInitThreads() 函式已從 C API 中移除。 它不應當被顯式地呼叫;請改用 PyOS_AfterFork_Child()。 (由 Victor Stinner 在 bpo-36728 中貢獻。)

  • 在 Unix 上,C 擴充套件不會再被連結到 libpython,但 Android 和 Cygwin 例外。 當 Python 被嵌入時,libpython 不可使用 RTLD_LOCAL 載入,而要改用 RTLD_GLOBAL。 之前使用 RTLD_LOCAL 已經不可能載入未連結到 libpython 的 C 擴充套件了,例如通過 Modules/Setup*shared* 部分構建的標準庫 C 擴充套件。 (由 Victor Stinner 在 bpo-21536 中貢獻。)

  • 在解析或構建值時(例如 PyArg_ParseTuple(), Py_BuildValue(), PyObject_CallFunction() 等等)使用形如 # 的格式而不定義 PY_SSIZE_T_CLEAN 現在將會引發 DeprecationWarning。 它將在 3.10 或 4.0 中被移除。 請參閱 語句解釋及變數編譯 瞭解詳情。 (由 Inada Naoki 在 bpo-36381 中貢獻。)

  • 堆分配型別的例項(例如使用 PyType_FromSpec() 建立的例項)會儲存一個對其型別物件的引用。 提升這些型別物件引用計數的操作已從 PyType_GenericAlloc() 移至更低層級的函式 PyObject_Init()PyObject_INIT()。 這使用通過This makes types created through PyType_FromSpec() 所建立型別的行為與管理程式碼中的其他類保持一致。

    靜態分配型別將不受影響。

    在大部分情況下,這應該都不會有附帶影響。 但是,在分配例項後手動提升引用計數的型別(也許是為了繞過漏洞)現在可能永遠不會被銷燬。 要避免這種情況,這些類需要在例項撤銷分配期間在型別物件上呼叫 Py_DECREF。

    要正確地將這些型別移植到 3.8,請應用以下修改:

    • 在分配例項之後在型別物件上移除 Py_INCREF —— 如果有的話。 這可以發生在呼叫 PyObject_New(), PyObject_NewVar(), PyObject_GC_New(), PyObject_GC_NewVar() 或任何其他使用 PyObject_Init()PyObject_INIT() 的自定義分配器之後。

      示例:

      static foo_struct *
      foo_new(PyObject *type) {
          foo_struct *foo = PyObject_GC_New(foo_struct, (PyTypeObject *) type);
          if (foo == NULL)
              return NULL;
      #if PY_VERSION_HEX < 0x03080000
          // Workaround for Python issue 35810; no longer necessary in Python 3.8
          PY_INCREF(type)
      #endif
          return foo;
      }
      

      確保所有堆分配型別的自定義 tp_dealloc 函式會減少型別的引用計數。

      static void
      foo_dealloc(foo_struct *instance) {
          PyObject *type = Py_TYPE(instance);
          PyObject_GC_Del(instance);
      #if PY_VERSION_HEX >= 0x03080000
          // This was not needed before Python 3.8 (Python issue 35810)
          Py_DECREF(type);
      #endif
      }
      
  • Py_DEPRECATED() 巨集已經針對 MSVC 實現。 這個巨集現在必須放在符號名稱之前。

    示例:

    Py_DEPRECATED(3.8) PyAPI_FUNC(int) Py_OldFunction(void);
    
  • 直譯器將不再假裝支援跨釋出版本的擴充套件型別二進位制相容性。 由第三方擴充套件模組所匯出的 PyTypeObject 應該具有當前 Python 版本所要求的所有空位,包括 tp_finalize (Py_TPFLAGS_HAVE_FINALIZE 不會再在讀取 tp_finalize 之前被檢查)。

    (由 Antoine Pitrou 在 bpo-32388 中貢獻。)

  • PyCode_New() 在第二個位置添加了新的形參 (posonlyargcount) 以支援 PEP 570,指明僅限位置引數的數量。

  • 函式 PyNode_AddChild()PyParser_AddToken() 現在接受兩個額外的 int 引數 end_linenoend_col_offset

  • The libpython38.a file to allow MinGW tools to link directly against python38.dll is no longer included in the regular Windows distribution. If you require this file, it may be generated with the gendef and dlltool tools, which are part of the MinGW binutils package:

    gendef python38.dll > tmp.def
    dlltool --dllname python38.dll --def tmp.def --output-lib libpython38.a
    

    已安裝的 pythonXY.dll 所在位置將取決於安裝選項以及 Windows 的版本和語言。 請參閱 在Windows上使用 Python 瞭解更多資訊。 該結果庫應當放在與 pythonXY.lib 相同的目錄下,這通常是你的 Python 安裝路徑下的 libs 目錄。

CPython 位元組碼的改變

  • 直譯器迴圈已通過將塊堆疊展開邏輯移入編譯器獲得了簡化。 編譯器現在會發出顯式指令來調整值堆疊併為 break, continuereturn 呼叫清除程式碼。

    移除了操作碼 BREAK_LOOP, CONTINUE_LOOP, SETUP_LOOPSETUP_EXCEPT。 添加了新的操作碼 ROT_FOUR, BEGIN_FINALLY, CALL_FINALLYPOP_FINALLY。 修改了 END_FINALLYWITH_CLEANUP_START 的行為。

  • 添加了新的操作碼 END_ASYNC_FOR 用於處理當等待 async for 迴圈的下一項時引發的異常。

  • MAP_ADD 現在會預期值為棧的第一個元素而鍵為第二個元素。 作出此改變以使得字典推導式能如 PEP 572 所提議的那樣,鍵總是會在值之前被求值。

演示和工具

  • 添加了一個檢測指令碼用於對訪問變數的不同方式進行計時: Tools/scripts/var_access_benchmark.py