Python 3.8穩定版于2019年10月14日發布,相信還有很多項目并沒有升級到3.8版本,今天有一個項目經理問我:我們項目組要升級到Python3.8,3.8和3.7有什么區別呢?你能不能跟我講一下。我問了他為什么要升級,然后對他說:你就當3.7用就行了。他對我說:你們肯定也要升級,你趕緊了解一下,然后給我們講一講。雖然python2.7的項目我們依然在開發,但是誰知道什么時候就得必須升級了呢。所以我就專門看了一下3.8的升級特性,在這里做一個記錄。以下為Python3.8相比3.7的新增特性。
賦值表達式
新增的語法 := 可在表達式內部為變量賦值。 它被昵稱為“海象運算符”因為它很像是海象的眼睛和長牙。
在這個示例中,賦值表達式可以避免調用 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]
不過這個運算符可讀性不高,因此一定要使用在比較清晰的場景
僅限位置形參
新增了一個函數形參語法 / 用來指明某些函數形參必須使用僅限位置而非關鍵字參數的形式。 這種標記語法與通過 help() 所顯示的使用 Larry Hastings 的 Argument Clinic 工具標記的 C 函數相同。
在下面的例子中,形參 a 和 b 為僅限位置形參,c 或 d 可以是位置形參或關鍵字形參,而 e 或 f 要求為關鍵字形參:
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不能是關鍵字參數
f(10, 20, 30, 40, 50, f=60) # e必須是關鍵字參數
這種標記形式的一個用例是它允許純 Python 函數完整模擬現有的用 C 代碼編寫的函數的行為。 例如,內置的 divmod() 函數不接受關鍵字參數:
def divmod(a, b, /):
"Emulate the built in divmod() function"
return (a // b, a % b)
另一個用例是在不需要形參名稱時排除關鍵字參數。 例如,內置的 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
用于已編譯字節碼文件的并行文件系統緩存
新增的 PYTHONPYCACHEPREFIX 設置 (也可使用 -X pycache_prefix) 可將隱式的字節碼緩存配置為使用單獨的并行文件系統樹,而不是默認的每個源代碼目錄下的 __pycache__ 子目錄。
緩存的位置會在 sys.pycache_prefix 中報告 (None 表示默認位置即 __pycache__ 子目錄)。
Python3.8之前會生成__pycache__子目錄
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 模塊提供了從第三方包讀取元數據的(臨時)支持。 例如,它可以提取一個已安裝軟件包的版本號、入口點列表等等:
>>> 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')]
這個在進行函數式編程的時候會用到,一般情況下使用比較少
改進的部分模塊
- asyncio
asyncio.run() 已經從暫定狀態晉級為穩定 API。 此函數可被用于執行一個 coroutine 并返回結果,同時自動管理事件循環
import asyncio
async def main():
await asyncio.sleep(0)
return 42
asyncio.run(main())
這大致等價于:
import asyncio
async def main():
await asyncio.sleep(0)
return 42
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
loop.run_until_complete(main())
finally:
asyncio.set_event_loop(None)
loop.close()
精簡了非常多的代碼,現在可以作為首推方式使用
- collections
collections.namedtuple() 的 _asdict() 方法現在將返回dict而不是 collections.OrderedDict。OrderedDict為有序字典,目前普通字典已經保證具有確定的元素順序。如果還需要 OrderedDict 的額外特性,建議的解決方案是將結果轉換為需要的類型: OrderedDict(nt._asdict())。
- cProfile
cProfile.Profile 類現在可被用作上下文管理器。 在運行時對一個代碼塊實現性能分析:
import cProfile
with cProfile.Profile() as profiler:
# code to be profiled
...
還有functools、inspect、itertools、operator等模塊,我們后面單獨介紹,這些都是Python中非常方便的模塊。