2020 年 python2 停止維護(hù),公司代碼規(guī)范也鼓勵使用 python3.6+版本,而隨著 Python 版本的不斷更新,許多舊的語法在可讀性與效率上都已經(jīng)有更好的替代了舰讹。當(dāng)然,大部分的重要特性独撇,例如裝飾器吨些、生成器、async 等,相信大家都非常熟悉了,這里就面向一些使用率稍微少一些夷野、日常所見代碼中不太常見的能用得上的語法做一個匯總,僅供參考。
日常的自用 Python 腳本沒有太大的工程壓力嚎莉,能緊跟更新步伐、嘗試新的特性沛豌。但是語法糖用的好就是效率提升趋箩,用的不好就是可讀性災(zāi)難,有些語法的出現(xiàn)也伴隨著種種的爭議加派,用更新的語法不代表就能寫出更好的代碼叫确。
翻看語言的更新日志確實(shí)蠻有意思
通過語法的更新變化還有變化帶來的爭議,也能窺透語言的設(shè)計哲學(xué)芍锦、匯聚濃縮在一個特定點(diǎn)上的社區(qū)開發(fā)經(jīng)驗(yàn)竹勉。選擇合適自己的、保持對代碼精簡可讀的追求才是最重要醉旦。
那么就從老到新饶米,理一理那些有意思的小 feature 吧〕岛可能有漏掉有趣的點(diǎn)檬输、也可能有解釋不到位的地方,歡迎各位大佬更正補(bǔ)充匈棘。
Python 3.0-3.6
PEP 3132 可迭代對象解包拓展
Python3.0 引入丧慈,加強(qiáng)了原本的星號運(yùn)算符(*),讓星號運(yùn)算符能夠智能地展開可迭代對象。
>>> 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 矩陣乘法運(yùn)算符
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]])
矩陣乘法運(yùn)算符的魔術(shù)方法為__matmul__()
吟税、__rmatmul__()
凹耙、__imatmul__()
三個
本身用處不大,但是提供了一個額外的操作符使用空間肠仪,可以用來重載來進(jìn)行類似距離計算之類的用途肖抱。
>>> 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
爭議主要存在于:作為矩陣乘法來說@操作符沒有直觀聯(lián)系、影響可讀性异旧,不如直接使用 matmul
PEP 3107/484/526 函數(shù)注解/類型提示/變量注解
Python3.0 引入函數(shù)注解意述、3.5 引入 typing,讓 python 也能享受靜態(tài)類型的福利吮蛹』绯纾可以說是 py3 中個人最喜歡的 feature,使用簡單匹涮、效果強(qiáng)大天试,直接讓開發(fā)效率以及代碼可維護(hù)性直線增長。
# 參數(shù)后加:即可標(biāo)注類型然低,函數(shù)結(jié)構(gòu)定義后接->即可標(biāo)注返回類型
def get_hello(name: str) -> str:
return f"Hello, {name}!"
如上進(jìn)行標(biāo)記之后 IDE 便能自動讀取參數(shù)喜每、返回類型,直接出聯(lián)想爽快如 java雳攘。
而 PEP 484 Typing 則是極大的擴(kuò)充了類型定義語法带兜,支持別名、泛型吨灭、Callable刚照、Union 等等。非常推薦直接閱讀 PEP喧兄。
下面就是一個泛型的例子
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)无畔,使用也很簡單,直接在變量后添加冒號和類型即可吠冤,搭配函數(shù)注解一起食用體驗(yàn)極佳
pi: float = 3.142
# 也同樣支持Union等
from typing import Union
a: Union[float,None] =1.0
3.7 中又引入了延遲標(biāo)記求值(PEP 563)浑彰,讓 typing 支持了前向引用、并減輕了標(biāo)注對程序啟動時間的影響拯辙,如虎添翼郭变。
# 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
靜態(tài)類型檢查對 Python 所帶來的副作用主要還是啟動時間上的影響,當(dāng)然大部分場景所帶來的便利是遠(yuǎn)大于這一副作用的诉濒。
PEP 498 f-string
Python3.6 引入周伦,應(yīng)該是用的最多的 feature 之一了,但是看到很多代碼里面還是 str.format未荒,就不得不再提一下专挪。
>>> a = 10
>>> #只需要簡單的在任意字符串字面量前加個f,就可以用花括號直接引用變量
>>> print(f"a = {a}")
a = 10
>>> # 格式化也很方便片排,使用:即可
>>> pi = 3.14159
>>> print(f"pi = {pi: .2f}")
pi = 3.14
也可以在表達(dá)式后接!s 或者!r 來選擇用 str()還是 repr()方法轉(zhuǎn)換為字符串狈蚤。
基本就是 str.format 的語法糖。在 3.8 版本以后划纽,又增加了直接套表達(dá)式的功能,輸出信息非常方便锌畸。
>>> theta = 30
>>> print(f'{theta=} {cos(radians(theta))=:.3f}')
theta=30 cos(radians(theta))=0.866
PEP 515 數(shù)值字面值下劃線
Python3.6 引入勇劣。輸入太長的數(shù)字字面值怎么辦?
>>> a = 123_456_789
>>> b = 123456789
>>> a == b
True
比較雞肋…
Python 3.7
PEP 557 數(shù)據(jù)類 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
對這個例子比默,這個類會自動生成以下魔術(shù)方法
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 其實(shí)已經(jīng)內(nèi)置了不少的類似模型:collection.namedtuple
盆犁、typing.NamedTuple
命咐、attrs
等
但是這條 PEP 的提出還是為了保證方便地創(chuàng)建資料類的同時,保證靜態(tài)類型檢查谐岁,而已有的方案都不方便直接使用檢查器醋奠。
Python 3.8
PEP 572 海象牙運(yùn)算符
"逼走"了 Guido van Rossum,最有爭議的 PEP 之一伊佃。首先引入了海象牙運(yùn)算符:=
窜司,代表行內(nèi)賦值。
# Before
while True:
command = input("> ");
if command == "quit":
break
print("You entered:", command)
# After
while (command := input("> ")) != "quit":
print("You entered:", command)
assignment expressions 在進(jìn)行分支判斷時非常好用航揉,寫的時候能夠舒服很多塞祈。本身使用也集中在 if/while 這種場景,雖然讓語法變復(fù)雜了帅涂,但是總體還是可控的情龄,舒適程度大于風(fēng)險沧奴。
海象運(yùn)算符本身問題不大,但是爭議主要存在于 PEP 572 的第二點(diǎn),對于生成器語義的變化沉填。
在 PEP 572 后,生成器的in
后的運(yùn)算順序產(chǎn)生了變化辫继,原本是作為生成器輸入腔召,結(jié)果現(xiàn)在變成了生成器閉包的一部分。
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))
這樣的修改目的是配合海象牙運(yùn)算符增加代碼可讀性,但無疑是帶破壞性的修改艇搀,且讓運(yùn)行順序變得迷惑尿扯,讓一些老代碼出現(xiàn)難以發(fā)現(xiàn)的 bug。
python 社區(qū)在激烈辯論后焰雕,這一部分的修改被成功撤銷衷笋,只保留了海象牙運(yùn)算符。
PEP 570 僅限位置形參
在函數(shù)形參處新增一個/
語法矩屁,劃分非關(guān)鍵字與關(guān)鍵字形參辟宗。例如
def f(a, b, /, c, d, *, e, f):
print(a, b, c, d, e, f)
# 以下調(diào)用均合法
f(10, 20, 30, d=40, e=50, f=60)
# 以下調(diào)用均不合法
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
/
語法的添加讓調(diào)用函數(shù)時可以在可讀性與簡潔之間自由選擇,可以選擇強(qiáng)制不接受關(guān)鍵字參數(shù)吝秕、不需要形參名稱時也可以省略泊脐。同時也讓接受任意參數(shù)函數(shù)的實(shí)現(xiàn)變得方便了許多,例如:
class Counter(dict):
def __init__(self, iterable=None, /, **kwds):
# Note "iterable" is a possible keyword argument
這條本來也有其他方案烁峭,例如裝飾器實(shí)現(xiàn)容客、def fn(.arg1, .arg2, arg3):
、def fn(a, (b, c), d):
等约郁,這里就不一一展開了缩挑,推薦閱讀 PEP 原文。
Python 3.9
PEP 584 字典合并運(yùn)算符
在此之前鬓梅,要想合并兩個字典的畫風(fēng)是這樣的
a={'a':1,'b':2}
b={'c':3}
a.update(b)
# 或者是
c = {**a, **b}
但自從有了|之后供置,可以變成這樣
a |= b
c = a | b
當(dāng)然這個操作符也伴隨著一些爭議,大概是這樣:
反方:合并不符合交換律 正方:python 字典合并本身就不符合交換律绽快,特別是 python3.6 之后統(tǒng)一到有序字典后芥丧,相比合并應(yīng)該更類似于拼接
反方:類似管道寫法進(jìn)行多次合并效率低,反復(fù)創(chuàng)建和銷毀臨時映射 正方:這種問題在序列級聯(lián)時同樣會出現(xiàn)谎僻。如果真出現(xiàn)了合并大量字典的使用場景娄柳,應(yīng)當(dāng)直接顯式循環(huán)合并
反方:|操作符容易和位運(yùn)算混淆。運(yùn)算符行為強(qiáng)依賴于變量種類艘绍,這在 python 是非常不利于可讀性的 正方:確實(shí)有這個問題赤拒,但是|已經(jīng)很混亂了(位運(yùn)算、集合操作诱鞠、__or__()
魔術(shù)方法重載)挎挖,所以還是先規(guī)范變量命名吧
即將到來的 Python 3.10
PEP 617 / bpo-12782 括號內(nèi)的上下文管理
這一條是針對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
):
...
比較實(shí)用蕉朵,避免了 with 下面接 with 產(chǎn)生不必要縮進(jìn)的尷尬。值得注意的是阳掐,這一條語法變動是新的非 LL(1)文法 CPython PEG 解析器所帶來的副產(chǎn)物始衅。所以 PEP 617 的標(biāo)題是New PEG parser for CPython
冷蚂。
PEP 634 結(jié)構(gòu)化模式匹配 match-case
直接上結(jié)構(gòu):
match subject:
case <pattern_1>:
<action_1>
case <pattern_2>:
<action_2>
case <pattern_3>:
<action_3>
case _:
<action_wildcard>
是不是感覺熟悉又臭名昭著的 switch-case 終于來了?當(dāng)然還是有區(qū)別的:
這個寫法基本還是 if-elif-else 的語法糖汛闸,運(yùn)行完 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")
結(jié)語
Python語言的發(fā)展是由技術(shù)方面的進(jìn)步别伏、工程方面的剛需匯聚而成的智慧結(jié)晶蹄衷,身在其中便能體會到來自碼農(nóng)們創(chuàng)造出的代碼設(shè)計之巧思、學(xué)問厘肮。只有盡可能多去理解各類語法意義愧口,才能讓開發(fā)變得流暢;了解語法的構(gòu)成與其爭議类茂,在計算機(jī)科學(xué)領(lǐng)域的視野才會豁然開朗调卑。與時俱進(jìn)才是真正的好碼神~這篇文章就到這里,如果對你有幫助的話不妨點(diǎn)贊大咱、收藏、轉(zhuǎn)發(fā)一下注益,歡迎在評論區(qū)交流以及提出寶貴意見碴巾,更多的Python實(shí)戰(zhàn)技巧,學(xué)習(xí)資料可以私信與我交流丑搔,我會盡我所能的提供幫助!