隨手扎
淺嘗Python3.9新特性
作為一個開發人員,最好固定一段時間就去了解一下有什麼新的東西可以使用。它可能不會馬上使用到,但是有印象會加速當需要使用時,尋找的時間。
這次,稍微看了一下Python 3.9和Python 3.10的相關改動,並玩玩紀錄一下一些我認為有點意思的部份。
現在Python的更新文件幾乎都有中文版,閱讀起來並不是那麼困難,但是有部份特性並沒有說的非常清楚,還是看一下PEP和親身使用下比較明白。(隨然還是有些注意到的特性,仍不知道是要做什麼的)。
Python 3.10編譯
為此,我直接下載Python 3.10來編譯使用,順便紀錄一下編譯過程。
./confiure --prefix=`pwd`/dist/
make
make test
make install
編譯過程還算順利,除了在test階段test_os是失敗的,並跳過了20多個測試,但不影響接者的使用。喔對,不知道哪裡下載原始碼的,可以在這個頁面找到。
Python 3.9 釋出
Python 3.9於2020年10月05日正式釋出。其中主要幾個新特性包含:
其中還有一些改動,並增加了幾個新的package。
dict增加合併運算符號
dist合併運算
直接拿範例來看:
x = {
"key1": "value1 from x",
"key2": "value2 from x",
}
y = {
"key2": "value2 from y",
"key3": "value3 from y",
}
x | y
# {'key1': 'value1 from x', 'key2': 'value2 from y', 'key3': 'value3 from y'}
y | x
# {'key1': 'value1 from x', 'key2': 'value2 from x', 'key3': 'value3 from y'}
※ Note: 字典物件是無序的,上面結果為了更好檢查,我手動調整了輸出的結果。
透過a|b
的操作,會產生新的一個dist,其為在第一個字典基礎上,更新或新增第二個字典物件欄位。這個操作又叫做合併(merge)。我看到第一個想法就是,這與ECMAScript的展開運算子(Spread syntax)有點像:
let x = {
"key1": "value1 from x",
"key2": "value2 from x",
}
let y = {
"key2": "value2 from y",
"key3": "value3 from y",
}
{...x, ...y}
// { key1: 'value1 from x', key2: 'value2 from y', key3: 'value3 from y' }
{...y, ...x}
// { key1: 'value1 from x', key2: 'value2 from x', key3: 'value3 from y' }
※ Note: 物件欄位是無序的,上面結果為了更好檢查,我手動調整了輸出的結果。
dist更新運算
合併運算會產生的新的物件。如果要更新,還需要重新指定值回去:
x = x | y
新的operator提供更簡單的作法:
x |= y
# {'key1': 'value1 from x', 'key2': 'value2 from y', 'key3': 'value3 from y'}
類型標注的放寬
有了mypy後,也多了typing package。Python允許我們對於變數、參數進行標示型別。但在3.9以前,整數正列必須使用typing.List[int]
來標注,現在,可以更簡單的使用內置型別來寫,寫成list[int]
。
不只是list,許多型別也都可以直接使用,而無須先引入typing
。比如字典的型別標注可以寫成dict[int, str]
,而不用寫成typgin.Dict[int, str]
※ 注意:型別標注只是標注,Python直譯器並不會在運行時檢查,你仍然需要像是mypy的工具在運行前檢測。
from typing import List
def showList(l: list): # 不標注內容元素類別
for element in l:
print(element, end=", ")
print()
def showIntList(l: list[int]): # 3.9以前不支持這樣的寫法
for element in l:
print(element, end=", ")
print()
def showIntTypingList(l: List[int]): # 3.9以前不支持這樣的寫法
for element in l:
print(element, end=", ")
print()
showList([1,2,3,4,5,"a", "b", "c"])
#=> 1, 2, 3, 4, 5, a, b, c,
showIntList([1,2,3,4,5])
#=> 1, 2, 3, 4, 5,
showIntTypingList([1,2,3,4,5])
#=> 1, 2, 3, 4, 5,
要注意的是這僅僅是標注,而非檢查。要檢查需要使用其他工具。但下面的依然會正常執行
showIntTypingList([1,2,3,4,5,"a", "b", "c"])
#=> 1, 2, 3, 4, 5, a, b, c,
showIntTypingList("hello world")
#=> h, e, l, l, o, , w, o, r, l, d,
可以引用自身類別
Python 3.9以前無法引用自身類別,要建立串列或樹狀結構還有點困難。
class Node:
def __init__(self, val, next_node: Node): # 3.9以前會報錯,找不到Node
self.val = val
self.next = next_node
裝飾器語法限制的放寬
與型別標注一樣,3.9以前的Python不允許裝飾器有下標操作。實際上應該還有其他限制,但現在只要是合法的表達是應該是都可以使用。以我理解自己嘗試了一下:
def D1(f):
def _f(*arg, **karg):
print("D1")
f(*arg, **karg)
return _f
def D2(f):
def _f(*arg, **karg):
print("D2")
f(*arg, **karg)
return _f
D = [D1, D2]
@D[0]
# 3.9以前不允許這樣寫
# 需要寫成:
# d_1 = D[0]
# @d_1
def hello(name):
print("Hello", name)
@D[1]
def world():
print("World")
所有合法的表達式都可以?那象牙運算符呢?
@d_2:=D[1]
def peko():
print("PEKO")
print(d_2)
#=> <function D2 at 0x7f68fd947790>
peko()
# D2
# PEKO
過了
字串移除前綴和和綴的方法
直接看官網範例:
'TestHook'.removeprefix('Test')
#=> 'Hook'
'BaseTestCase'.removeprefix('Test')
#=> 'BaseTestCase'
############
'MiscTests'.removesuffix('Tests')
#=> 'Misc'
'TmpDirMixin'.removesuffix('Tests')
#=> 'TmpDirMixin'
覺得沒什麼好多提的。就很像ljust
和rjust
的反過來,或是rstrip
和lstrip
的像似用法。不管怎說,removeprefix
和removesuffix
語意上非常好理解,內置有了就是爽!
小提一下
Python 3.10 的int.bit_count()
int.bit_count()前陣子剛好看到,並了解了一下相關的演算法12。貌似機算機領域在底層經常使用到。但最間單的影響是LeetCode有些題目會更好寫了wwww。
新增的package
- zoneinfo: IANA 時區資料庫
- graphlib: 粗淺理解是極為簡單的資料結構–圖的小工具。看起來還很陽春。
改動
- 使用了新的解析器
- ast package做了一些改動
- parser package可能將被棄用
…之前還想要用這兩個來玩一點魔法…
- compileall
- concurrent.futures
- curses
不看不知道Python已經封裝了curses…我以前好像還在找三方套件… - fcntl
- hashlib
- inspect
這也是我之前想在進一步深入玩玩看的package。 - ipaddress
支持IPv6拉! - typing
引入typing.Annotated
。PEP 593,靈活的函數和變量標注。總之我沒看很明白,可能和Java的Annotation對運作上沒有太大實際作用,只是讓程式更為明確的手段。
小節
總之,這次收穫還不小。發現不少以前存在卻不認識的package,字典的合併運算也很讓人興奮。也先紀錄了一下一些以後可能可以在深入玩玩的package,看看別人到底是如何實現這樣功能的。