python教程
在人工智慧的風口,Python越來越受歡迎,很多小夥伴也紛紛想要開始學習Python。作為一個Python近2年的學習者,為大家獻上一些不錯的Python自學乾貨。
這是我在自學路上,整理的不錯的Python自學資料。需要的小夥伴可以掃描下方二維碼: python教程
因為加的人多,大家一定要填寫備註:{333} 否則不通過,請理解。通過後,請主動領取。
作者:袁昊 騰訊專項技術測試工程師python教程
2020年python2停止維護,而隨著Python版本的不斷更新,許多舊的語法在可讀性與效率上都已經有更好的替代了。當然,大部分的重要特性,例如裝飾器、生成器、async等,相信大家都已經瞭然於心,這裡就對一些用的稍微少一些、日常看到的代碼中不太常見的能用得上的語法做一個簡單的筆記,供大家參考。經驗有限,見解甚淺,還望各位大佬們多多指導、補充。
日常的自用Python腳本沒有太大的工程壓力,能緊跟更新步伐、嘗試新的特性。但是語法糖用的好就是效率提升,用的不好就是可讀性災難,有些語法的出現也伴隨著種種的爭議,用更新的語法不代表能寫出更好的代碼。
翻看語言的更新日誌確實蠻有意思
通過語法的更新變化還有變化帶來的爭議,也能窺透語言的設計哲學、匯聚濃縮在一個特定點上的社區開發經驗。選擇合適自己的、保持對代碼精簡可讀的追求才是最重要。
那麼就從老到新,理一理那些有意思的小feature吧。可能有漏掉有趣的點、也可能有解釋不到位的地方,歡迎各位大佬更正補充。
Python 3.0-3.6PEP 3132 可迭代對象解包拓展Python3.0引入,加強了原本的星號運算符(*),讓星號運算符能夠智能地展開可迭代對象。
>>> a, *b, c = range(5)>>> a0>>> c4>>> 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 @ b140>>> c = numpy.array([[10, 15], [20, 25], [30, 35]])>>> d = numpy.array([[4, 5, 6], [7, 8, 9]])>>> c @ darray([[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, TupleT = 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 Uniona: 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-stringPython3.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 == bTrue
比較雞肋...
Python 3.7PEP 557 數據類Data Classes提供了一個方便的dataclass類裝飾器,直接上代碼舉例:
from dataclasses import dataclass@dataclassclass 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_handdef __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 NotImplementeddef __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 NotImplementeddef __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 NotImplementeddef __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 NotImplementeddef __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 NotImplementeddef __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.8PEP 572 海象牙運算符"逼走"了Guido van Rossum,最有爭議的PEP之一。首先引入了海象牙運算符:=,代表行內賦值。
# Beforewhile True: command = input("> "); if command == "quit": break print("You entered:", command) # Afterwhile (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))# 等價於# Beforetemp_list = ["abc", "bcd"]def func_data(data: int): for x in range(data): yield xresult_list = func_data(len(temp_list))print(list(result_list))# Aftertemp_list = ["abc", "bcd"]def func_data(): for x in range(len(temp_list)): yield xresult_list = func_data()print(list(result_list))
這樣的修改目的是配合海象牙運算符增加代碼可讀性,但無疑是帶破壞性的修改,且讓運行順序變得迷惑,讓一些老代碼出現難以發現的bug。
python社區在激烈辯論後,這一部分的修改被成功撤銷,只保留了海象牙運算符。
關於這個PEP,知乎上有難得一見的有價值討論,這部分範例代碼也引用自此:
https://www.zhihu.com/question/274823057/answer/376917512
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 argumentf(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.9PEP 584 字典合併運算符在此之前,要想合併兩個字典的畫風是這樣的
a={'a':1,'b':2}b={'c':3}a.update(b)# 或者是c = {**a, **b}
但自從有了|之後,可以變成這樣
a |= bc = a | b
當然這個操作符也伴隨著一些爭議,大概是這樣:
反方:合併不符合交換律 正方:python字典合併本身就不符合交換律,特別是python3.6之後統一到有序字典後,相比合併應該更類似於拼接
反方:類似管道寫法進行多次合併效率低,反覆創建和銷毀臨時映射 正方:這種問題在序列級聯時同樣會出現。如果真出現了合併大量字典的使用場景,應當直接顯式循環合併
反方:|操作符容易和位運算混淆。運算符行為強依賴於變量種類,這在python是非常不利於可讀性的 正方:確實有這個問題,但是|已經很混亂了(位運算、集合操作、__or__()魔術方法重載),所以還是先規範變量命名吧
即將到來的Python 3.10PEP 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 <pattern_1>: <action_1> case <pattern_2>: <action_2> case <pattern_3>: <action_3> case _: <action_wildcard>
是不是感覺熟悉又臭名昭著的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) tuplematch 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")
結語語言的發展是由技術的進步、工程的需求凝結出的結晶,從中透露出的是滿滿的代碼設計哲學。充分了解語法,可以讓開發變得順暢舒適;理解了語法背後的原因與爭議,則可以開拓計算機科學領域的視野。與時俱進,深入了解各種新興技術,才是真正的極客~
推薦閱讀What's new in Python https://docs.python.org/zh-cn/3.10/whatsnew/index.html