“人生苦短,我用Python”,雖然說大量數學和統計分析庫是一個重要優勢,但是歸根結底,Python的最大優勢就是三點:
但是通常一般來講,當扯到并發的時候,無論是多服務器、多進程、多線程、還是協程,事情都難以避免的開始變得復雜。我們把目光放到并發的最輕量級實現:協程上面,簡單淺談一下關于迭代器。
從迭代器說起
迭代器是訪問集合元素的一種方式,無需知道集合的內部結構。任何實現了__iter__
和__next__
方法的對象都是迭代器。迭代器使得你可以逐個遍歷容器內的元素,直到沒有更多元素時拋出StopIteration
異常。可以很容易實現一個自定義迭代器,這在自己實現一些需要迭代的數據結構式會有些作用:
class MyIterator:def __init__(self, max):self.current = 0self.max = maxdef __iter__(self):return selfdef __next__(self):if self.current >= self.max:raise StopIterationvalue = self.currentself.current += 1return value# 使用迭代器
for i in MyIterator(5):print(i)
輸出:
0
1
2
3
4
迭代的藝術:生成器
生成器(Generators)是Python中一種特殊的迭代器。與傳統的通過列表、元組等數據結構存儲所有元素不同,生成器僅在需要時生成下一個值,大大節省了內存空間。生成器自動實現了迭代器協議,使得編寫迭代器更為簡單且內存效率更高。
如果想要自行通過迭代器實現與生成器相同功能,也并不困難,如下例子中調用__next__方法與生成器作用類似:
class FibonacciIterator:def __init__(self, max_count):self.max_count = max_countself.current_count = 0self.prev = 0self.curr = 1def __iter__(self):return selfdef __next__(self):if self.current_count >= self.max_count:raise StopIterationelse:self.current_count += 1if self.current_count == 1:return self.prevelif self.current_count == 2:return self.currelse:self.prev, self.curr = self.curr, self.prev + self.currreturn self.curr# 使用自定義迭代器
fib_iter = FibonacciIterator(10)
print(fib_iter.__next__())
print(fib_iter.__next__())
print(fib_iter.__next__())
print(fib_iter.__next__())
print(fib_iter.__next__())
print(fib_iter.__next__())
print(fib_iter.__next__())
輸出:
0
1
1
2
3
5
8
生成器最簡單的形式是使用函數定義中的yield
語句。每當遇到yield
,函數就會暫停并返回一個值給調用者;當再次調用生成器的next()
方法時,它會從上次停止的地方繼續執行。上面的迭代器程序略顯復雜,但是使用生成器之后,程序則變得簡單清晰的多。下面的例子使用生成器實現了一樣的功能,這可以很好的幫助理解迭代器的運行順序:
def fibonacci_generator(max_count):prev, curr = 0, 1count = 0while count < max_count:yield prevprev, curr = curr, prev + currcount += 1# 使用生成器
for num in fibonacci_generator(10):print(num)
輸出:
0
1
1
2
3
5
8
13
21
34
Python的協程
在Python中,協程(Coroutines)通常被認為是生成器(Generators)的一個超集。它利用生成器的暫停與恢復機制,實現了非阻塞式的并發執行。協程在生成器的基礎上進行了擴展,增加了更多的控制流特性,如能夠接收輸入、暫停執行等。協程是通過生成器實現的,生成器中的yield
關鍵字可以用來實現協程的暫停和恢復執行的功能。Python協程實現,會使用asyncio包,并配合async/await
語法糖。
可能只是我個人的YY,似乎Python的協程或多或少的借鑒了JS。從語法到實現方式都非常類似。在現代ES中,async/await
語法糖是必不可少,幾乎每個程序都會用到的,而Python也是用相同的關鍵字,從標準化角度,JS先于Python標準化了這兩個語法糖。
一個簡單的Python使用asyncio的例子如下所示:
import asyncioasync def task(name, delay):print(f"{name} start")await asyncio.sleep(delay)print(f"{name} end")async def main():task1 = asyncio.create_task(task("TAsk1", 2))task2 = asyncio.create_task(task("TaSK2", 1))await task1await task2asyncio.run(main()
輸出:
TAsk1 start
TaSK2 start
TaSK2 end
TAsk1 end
上面代碼中,通過asyncio.create_task創建了兩個小任務,任務是異步運行的,并且程序必須等待兩個任務都結束后,main才會返回。注意這里asyncio.create_task創建的task有些類似于JS的:
new Promise().then()
所以兩個task是異步的。如果希望等待Task1完成后再運行Task2,則不需要通過asyncio.create_task創建task,直接再async函數調用前加入await即可,這是JS中一個常用的寫法。如下:
import asyncioasync def main():await task("TAsk1", 2)await task("TaSK2", 1)asyncio.run(main())
輸出:
TAsk1 start
TAsk1 end
TaSK2 start
TaSK2 end
總結
從迭代器,到生成器,最后到協程,理解這個演化路徑,可以更好地理解Python的協程機制。在此之后,加上對asyncio庫的了解,即可熟練掌握Pythong的協程機制了。