最新總結:2021那些小眾精巧的 Python 語法彙總
2020 年 python2 停止維護,公司程式碼規範也鼓勵使用 python3。6+版本,而隨著 Python 版本的不斷更新,許多舊的語法在可讀性與效率上都已經有更好的替代了。當然,大部分的重要特性,例如裝飾器、生成器、async 等,相信大家都非常熟悉了,這裡就面向一些使用率稍微少一些、日常所見程式碼中不太常見的能用得上的語法做一個彙總,僅供參考。
日常的自用 Python 指令碼沒有太大的工程壓力,能緊跟更新步伐、嘗試新的特性。但是語法糖用的好就是效率提升,用的不好就是可讀性災難,有些語法的出現也伴隨著種種的爭議,用更新的語法不代表就能寫出更好的程式碼。
翻看語言的更新日誌確實蠻有意思
透過語法的更新變化還有變化帶來的爭議,也能窺透語言的設計哲學、匯聚濃縮在一個特定點上的社群開發經驗。選擇合適自己的、保持對程式碼精簡可讀的追求才是最重要。
那麼就從老到新,理一理那些有意思的小 feature 吧。可能有漏掉有趣的點、也可能有解釋不到位的地方,歡迎各位大佬更正補充。
Python 3。0-3。6
PEP 3132 可迭代物件解包拓展
Python3。0 引入,加強了原本的星號運算子(*),讓星號運算子能夠智慧地展開可迭代物件。
>>> a, *b, c = range(5)
>>> a
0
>>> c
4
>>> b
[1, 2, 3]
隱式賦值也同樣適用
>>> for a, *b in [(1, 2, 3), (4, 5, 6, 7)]:
>>> print(b)
[2, 3]
[5, 6, 7]
注意雙星號(**)不能用相同語法展開字典
人畜無害,用處也不大的一個 feature
PEP 465 矩陣乘法運算子
Python3。5 引入,顧名思義,使用@符號。直接支援 numpy、pandas 等使用。
>>> a = numpy。array([1, 2, 3])
>>> b = numpy。array([10, 20, 30])
>>> a @ b
140
>>> c = numpy。array([[10, 15], [20, 25], [30, 35]])
>>> d = numpy。array([[4, 5, 6], [7, 8, 9]])
>>> c @ d
array([[145, 170, 195],
[255, 300, 345],
[365, 430, 495]])
矩陣乘法運算子的魔術方法為
__matmul__()、__rmatmul__()、__imatmul__()三個
本身用處不大,但是提供了一個額外的運算子使用空間,可以用來過載來進行類似距離計算之類的用途。
>>> from math import sqrt
>>> class Point:
>>> def __init__(self, x, y):
>>> self。x = x
>>> self。y = y
>>>
>>> def __matmul__(self, value):
>>> x_sub = self。x - value。x
>>> y_sub = self。y - value。y
>>> return sqrt(x_sub**2 + y_sub**2)
>>>
>>> a = Point(1, 3)
>>> b = Point(4, 7)
>>> print(a @ b)
5
爭議主要存在於:作為矩陣乘法來說@運算子沒有直觀聯絡、影響可讀性,不如直接使用 matmul
PEP 3107/484/526 函式註解/型別提示/變數註解
Python3。0 引入函式註解、3。5 引入 typing,讓 python 也能享受靜態型別的福利。可以說是 py3 中個人最喜歡的 feature,使用簡單、效果強大,直接讓開發效率以及程式碼可維護性直線增長。
# 引數後加:即可標註型別,函式結構定義後接->即可標註返回型別
def get_hello(name: str) -> str:
return f“Hello, {name}!”
如上進行標記之後 IDE 便能自動讀取引數、返回型別,直接出聯想爽快如 java。
而 PEP 484 Typing 則是極大的擴充了型別定義語法,支援別名、泛型、Callable、Union 等等。非常推薦直接閱讀 PEP。
https://www。python。org/dev/peps/pep-0484/
下面就是一個泛型的例子
from typing import TypeVar, Iterable, Tuple
T = TypeVar(‘T’, int, float, complex)
Vector = Iterable[Tuple[T, T]]
def inproduct(v: Vector[T]) -> T:
return sum(x*y for x, y in v)
def dilate(v: Vector[T], scale: T) -> Vector[T]:
return ((x * scale, y * scale) for x, y in v)
vec = [] # type: Vector[float]
隨後在 3。6 引入了眾望所歸的變數註解(PEP 526),使用也很簡單,直接在變數後新增冒號和型別即可,搭配函式註解一起食用體驗極佳
pi: float = 3。142
# 也同樣支援Union等
from typing import Union
a: Union[float,None] =1。0
3。7 中又引入了延遲標記求值(PEP 563),讓 typing 支援了前向引用、並減輕了標註對程式啟動時間的影響,如虎添翼。
# 3。7前合法
class Tree:
def __init__(self, left: ‘Tree’, right: ‘Tree’):
self。left = left
self。right = right
# 3。7前不合法、3。7後合法
class Tree:
def __init__(self, left: Tree, right: Tree):
self。left = left
self。right = right
更多的 python 型別檢查示例程式碼:
https://github。com/realpython/materials/tree/master/python-type-checking
靜態型別檢查對 Python 所帶來的副作用主要還是啟動時間上的影響,當然大部分場景所帶來的便利是遠大於這一副作用的。
PEP 498 f-string
Python3。6 引入,應該是用的最多的 feature 之一了,但是看到很多程式碼裡面還是 str。format,就不得不再提一下。
>>> a = 10
>>> #只需要簡單的在任意字串字面量前加個f,就可以用花括號直接引用變數
>>> print(f“a = {a}”)
a = 10
>>> # 格式化也很方便,使用:即可
>>> pi = 3。14159
>>> print(f“pi = {pi: 。2f}”)
pi = 3。14
也可以在表示式後接!s 或者!r 來選擇用 str()還是 repr()方法轉換為字串。
基本就是 str。format 的語法糖。在 3。8 版本以後,又增加了直接套表示式的功能,輸出資訊非常方便。
>>> theta = 30
>>> print(f‘{theta=} {cos(radians(theta))=:。3f}’)
theta=30 cos(radians(theta))=0。866
PEP 515 數值字面值下劃線
Python3。6 引入。輸入太長的數字字面值怎麼辦?
>>> a = 123_456_789
>>> b = 123456789
>>> a == b
True
比較雞肋…
Python 3。7
PEP 557 資料類 Data Classes
提供了一個方便的 dataclass 類裝飾器,直接上程式碼舉例:
from dataclasses import dataclass
@dataclass
class InventoryItem:
name: str
unit_price: float
quantity_on_hand: int = 0
def total_cost(self) -> float:
return self。unit_price * self。quantity_on_hand
對這個例子,這個類會自動生成以下魔術方法
def __init__(self, name: str, unit_price: float, quantity_on_hand: int = 0) -> None:
self。name = name
self。unit_price = unit_price
self。quantity_on_hand = quantity_on_hand
def __repr__(self):
return f‘InventoryItem(name={self。name!r}, unit_price={self。unit_price!r}, quantity_on_hand={self。quantity_on_hand!r})’
def __eq__(self, other):
if other。__class__ is self。__class__:
return (self。name, self。unit_price, self。quantity_on_hand) == (other。name, other。unit_price, other。quantity_on_hand)
return NotImplemented
def __ne__(self, other):
if other。__class__ is self。__class__:
return (self。name, self。unit_price, self。quantity_on_hand) != (other。name, other。unit_price, other。quantity_on_hand)
return NotImplemented
def __lt__(self, other):
if other。__class__ is self。__class__:
return (self。name, self。unit_price, self。quantity_on_hand) < (other。name, other。unit_price, other。quantity_on_hand)
return NotImplemented
def __le__(self, other):
if other。__class__ is self。__class__:
return (self。name, self。unit_price, self。quantity_on_hand) <= (other。name, other。unit_price, other。quantity_on_hand)
return NotImplemented
def __gt__(self, other):
if other。__class__ is self。__class__:
return (self。name, self。unit_price, self。quantity_on_hand) > (other。name, other。unit_price, other。quantity_on_hand)
return NotImplemented
def __ge__(self, other):
if other。__class__ is self。__class__:
return (self。name, self。unit_price, self。quantity_on_hand) >= (other。name, other。unit_price, other。quantity_on_hand)
return NotImplemented
這一條 PEP 也是比較有爭議的,主要原因是 Python 其實已經內建了不少的類似模型:
collection。namedtuple、typing。NamedTuple、attrs等
但是這條 PEP 的提出還是為了保證方便地建立資料類的同時,保證靜態型別檢查,而已有的方案都不方便直接使用檢查器。
Python 3。8
PEP 572 海象牙運算子
“逼走”了 Guido van Rossum,最有爭議的 PEP 之一。首先引入了海象牙運算子:=,代表行內賦值。
# Before
while True:
command = input(“> ”);
if command == “quit”:
break
print(“You entered:”, command)
# After
while (command := input(“> ”)) != “quit”:
print(“You entered:”, command)
assignment expressions 在進行分支判斷時非常好用,寫的時候能夠舒服很多。本身使用也集中在 if/while 這種場景,雖然讓語法變複雜了,但是總體還是可控的,舒適程度大於風險。
海象運算子本身問題不大,但是爭議主要存在於 PEP 572 的第二點,對於生成器語義的變化。
在 PEP 572 後,生成器的in後的運算順序產生了變化,原本是作為生成器輸入,結果現在變成了生成器閉包的一部分。
temp_list = [“abc”,“bcd”]
result_list = (x for x in range(len(temp_list)))
print(list(result_list))
# 等價於
# Before
temp_list = [“abc”, “bcd”]
def func_data(data: int):
for x in range(data):
yield x
result_list = func_data(len(temp_list))
print(list(result_list))
# After
temp_list = [“abc”, “bcd”]
def func_data():
for x in range(len(temp_list)):
yield x
result_list = func_data()
print(list(result_list))
這樣的修改目的是配合海象牙運算子增加程式碼可讀性,但無疑是帶破壞性的修改,且讓執行順序變得迷惑,讓一些老程式碼出現難以發現的 bug。
python 社群在激烈辯論後,這一部分的修改被成功撤銷,只保留了海象牙運算子。
PEP 570 僅限位置形參
在函式形參處新增一個/語法,劃分非關鍵字與關鍵字形參。例如
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
/語法的新增讓呼叫函式時可以在可讀性與簡潔之間自由選擇,可以選擇強制不接受關鍵字引數、不需要形參名稱時也可以省略。同時也讓接受任意引數函式的實現變得方便了許多,例如:
class Counter(dict):
def __init__(self, iterable=None, /, **kwds):
# Note “iterable” is a possible keyword argument
這條本來也有其他方案,例如裝飾器實現、def fn(。arg1, 。arg2, arg3):、def fn(a, (b, c), d):等,這裡就不一一展開了,推薦閱讀 PEP 原文。
Python 3。9
PEP 584 字典合併運算子
在此之前,要想合併兩個字典的畫風是這樣的
a={‘a’:1,‘b’:2}
b={‘c’:3}
a。update(b)
# 或者是
c = {**a, **b}
但自從有了|之後,可以變成這樣
a |= b
c = a | b
當然這個運算子也伴隨著一些爭議,大概是這樣:
反方:合併不符合交換律 正方:python 字典合併本身就不符合交換律,特別是 python3。6 之後統一到有序字典後,相比合並應該更類似於拼接
反方:類似管道寫法進行多次合併效率低,反覆建立和銷燬臨時對映 正方:這種問題在序列級聯時同樣會出現。如果真出現了合併大量字典的使用場景,應當直接顯式迴圈合併
反方:|運算子容易和位運算混淆。運算子行為強依賴於變數種類,這在 python 是非常不利於可讀性的 正方:確實有這個問題,但是|已經很混亂了(位運算、集合操作、__or__()魔術方法過載),所以還是先規範變數命名吧
即將到來的 Python 3。10
PEP 617 / bpo-12782 括號內的上下文管理
這一條是針對with語法(PEP 343)的小變動,讓一個with可以管理多個上下文。使用也很簡單
with (CtxManager() as example):
。。。
with (
CtxManager1(),
CtxManager2()
):
。。。
with (CtxManager1() as example,
CtxManager2()):
。。。
with (CtxManager1(),
CtxManager2() as example):
。。。
with (
CtxManager1() as example1,
CtxManager2() as example2
):
。。。
比較實用,避免了 with 下面接 with 產生不必要縮排的尷尬。值得注意的是,這一條語法變動是新的非 LL(1)文法 CPython PEG 解析器所帶來的副產物。所以 PEP 617 的標題是
New PEG parser for CPython
。
PEP 634 結構化模式匹配 match-case
直接上結構:
match subject:
case
case
case
case _:
是不是感覺熟悉又臭名昭著的 switch-case 終於來了?當然還是有區別的:
這個寫法基本還是 if-elif-else 的語法糖,執行完 case 就自動 break 出來。再加上一些看著不錯的模式匹配特性。
def http_error(status):
match status:
case 400:
return “Bad request”
case 401 | 403 | 404:
return “Not allowed”
case 404:
return “Not found”
case 418:
return “I‘m a teapot”
case _:
return “Something’s wrong with the Internet”
這樣的寫法看著就比 if-elif-else 看著清爽了許多。針對元組、類、列表也有不錯的支援:
# point is an (x, y) tuple
match point:
case (0, 0):
print(“Origin”)
case (0, y):
print(f“Y={y}”)
case (x, 0):
print(f“X={x}”)
case (x, y):
print(f“X={x}, Y={y}”)
case _:
raise ValueError(“Not a point”)
寫在最後
Python語言的發展是由技術方面的進步、工程方面的剛需匯聚而成的智慧結晶,身在其中便能體會到來自碼農們創造出的程式碼設計之巧思、學問。只有儘可能多去理解各類語法意義,才能讓開發變得流暢;瞭解語法的構成與其爭議,在計算機科學領域的視野才會豁然開朗。與時俱進才是真正的好碼神~這篇文章就到這裡,如果對你有幫助的話不妨點贊、收藏、轉發一下,歡迎在評論區交流以及提出寶貴意見,更多的Python實戰技巧,學習資料可以私信與我交流,我會盡我所能的提供幫助!
相關文章
- 2021-09-15後端程式設計Python3-高階程式設計(面向物件-下)
- 2021-09-12總結下ThinkPHP的程式碼審計方法
- 2021-09-11檸檬的有趣幽默笑話集錦(五十三)
- 2021-05-18透過呼叫約定解決一個常見問題
- 2021-05-05翻好友相簿不小心看到. . .忍不住哈 哈 哈