“人生苦短,我用Python”,虽然说大量数学和统计分析库是一个重要优势,但是归根结底,Python的最大优势就是三点:
但是通常一般来讲,当扯到并发的时候,无论是多服务器、多进程、多线程、还是协程,事情都难以避免的开始变得复杂。我们把目光放到并发的最轻量级实现:协程上面,简单浅谈一下关于迭代器。
从迭代器说起
迭代器是访问集合元素的一种方式,无需知道集合的内部结构。任何实现了__iter__
和__next__
方法的对象都是迭代器。迭代器使得你可以逐个遍历容器内的元素,直到没有更多元素时抛出StopIteration
异常。可以很容易实现一个自定义迭代器,这在自己实现一些需要迭代的数据结构式会有些作用:
class MyIterator:
def __init__(self, max):
self.current = 0
self.max = max
def __iter__(self):
return self
def __next__(self):
if self.current >= self.max:
raise StopIteration
value = self.current
self.current += 1
return 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_count
self.current_count = 0
self.prev = 0
self.curr = 1
def __iter__(self):
return self
def __next__(self):
if self.current_count >= self.max_count:
raise StopIteration
else:
self.current_count += 1
if self.current_count == 1:
return self.prev
elif self.current_count == 2:
return self.curr
else:
self.prev, self.curr = self.curr, self.prev + self.curr
return 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, 1
count = 0
while count < max_count:
yield prev
prev, curr = curr, prev + curr
count += 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 asyncio
async 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 task1
await task2
asyncio.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 asyncio
async def main():
await task("TAsk1", 2)
await task("TaSK2", 1)
asyncio.run(main())
输出:
TAsk1 start
TAsk1 end
TaSK2 start
TaSK2 end
总结
从迭代器,到生成器,最后到协程,理解这个演化路径,可以更好地理解Python的协程机制。在此之后,加上对asyncio库的了解,即可熟练掌握Pythong的协程机制了。