Почему вы до сих пор не знаете, как использовать сопрограммы Python

Python

в предыдущей статье«В одной статье подробно рассматриваются концепции Python Iterable, Iterator и Generator»В тексте известно, что генератор (Generator) можно определить двумя способами:

  • Список генератор
  • использоватьyieldопределенная функция

существуетPythonВ более ранних версиях сопрограммы также реализовывались через генераторы, то есть сопрограммы на основе генератора. В конце предыдущей статьи о генераторах приведен пример производителя-потребителя, который реализован на основе сопрограмм-генераторов.

def producer(c):
    n = 0
    while n < 5:
        n += 1
        print('producer {}'.format(n))
        r = c.send(n)
        print('consumer return {}'.format(r))


def consumer():
    r = ''
    while True:
        n = yield r
        if not n:
            return
        print('consumer {} '.format(n))
        r = 'ok'


if __name__ == '__main__':
    c = consumer()
    next(c)  # 启动consumer
    producer(c)

Прочитав этот код, я считаю, что многим новичкам, таким как я, на самом деле очень сложно написать собственный код сопрограммы в соответствии с бизнесом.PythonРазработчики также знают об этой проблеме, потому что она слишкомPythonic.而基于生成器的协程也将被废弃,因此本文将重点介绍asyncioИспользование пакета, а также некоторым из концепций, связанных с классами.
Примечание: я используюPythonОкружающая среда 3.7.

0x00 Что такое сопрограмма (сопрограмма)

сопрограмма (Coroutine) выполняется в потоке, который можно понимать как микропоток, но переключение сопрограммы не требует потребления контекста и легче, чем поток. Сопрограмма может прервать себя в любой момент, чтобы позволить другой сопрограмме начать выполнение, или она может возобновить прерывание и продолжить выполнение.Планирование между ними контролируется программистом (см. код производителя-потребителя в начале этой статьи). . ).

Определите расширение

существуетPythonДобавлена ​​версия 3.5+aysncиawaitКлючевые слова, эти два синтаксических сахара позволяют нам очень удобно определять и использовать сопрограммы.
используется в определении функцииasyncОбъявление определяет сопрограмму.

import asyncio

# 定义了一个简单的协程
async def simple_async():
    print('hello')
    await asyncio.sleep(1) # 休眠1秒
    print('python')
    
# 使用asynio中run方法运行一个协程
asyncio.run(simple_async())

# 执行结果为
# hello
# python

В сопрограмме, если вы хотите вызвать другую сопрограмму, используйтеawait.обращать вниманиеawaitключевое слово, которое должно быть вasyncиспользуется в определенной функции, которая, в свою очередь,asyncфункция может не появитьсяawait

# 定义了一个简单的协程
async def simple_async():
    print('hello')
    
asyncio.run(simple_async())

# 执行结果
# hello

asyncio.run()Будет запускать входящую сопрограмму, отвечающую за управлениеasyncioцикл событий.
Кромеrun()В дополнение к прямому выполнению сопрограмм методы также могут использовать циклы событий.loop

async def do_something(index):
    print(f'start {time.strftime("%X")}', index)
    await asyncio.sleep(1)
    print(f'finished at {time.strftime("%X")}', index)


def test_do_something():
    # 生成器产生多个协程对象
    task = [do_something(i) for i in range(5)]

    # 获取一个事件循环对象
    loop = asyncio.get_event_loop()
    # 在事件循环中执行task列表
    loop.run_until_complete(asyncio.wait(task))
    loop.close()

test_do_something()

# 运行结果
# start 00:04:03 3
# start 00:04:03 4
# start 00:04:03 1
# start 00:04:03 2
# start 00:04:03 0
# finished at 00:04:04 3
# finished at 00:04:04 4
# finished at 00:04:04 1
# finished at 00:04:04 2
# finished at 00:04:04 0

Видно, что все сопрограммы запускаются практически одновременно.
На самом деле, прочитайте исходный код, чтобы знатьasyncio.run()Реализация также инкапсулированаloopобъекты и их вызовы. иasyncio.run()Каждый раз создается новый объект цикла событий для выполнения сопрограммы.

0x01 Ожидаемый объект

существуетPythonможет подождать (Awaitable) объекты: сопрограмма (corountine),Задача(Task),Future. то есть эти объекты могут быть использованыawaitКлючевые слова для вызова

await awaitable_object
1. Корутина

сопрограмма отasync defДекларативно определить, что одна сопрограмма может использоваться другой сопрограммойawaitпозвонить

async def nested():
    print('in nested func')
    return 13


async def outer():

    # 要使用await 关键字 才会执行一个协程函数返回的协程对象
    print(await nested())

asyncio.run(outer())

# 执行结果
# in nested func
# 13

если вouter()вызов непосредственно в методеnested()без использованияawait, Будет бросатьRuntimeWarning

async def outer():
    # 直接调用协程函数不会发生执行,只是返回一个 coroutine 对象
    nested()
    
asyncio.run(outer())

Запустите программу, консоль выведет следующую информацию

RuntimeWarning: coroutine 'nested' was never awaited
  nested()
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
2. Задача

Задача(Task) можно использовать дляодновременноВыполнить сопрограмму. можно использоватьasyncio.create_task()Упакуйте объект расширения в задачи, которые вскоре будут помещены в очередь планирования и выполнены.

async def nested():
    print('in nested func')
    return 13

async def create_task():
    # create_task 将一个协程对象打包成一个 任务时,该协程就会被自动调度运行
    task = asyncio.create_task(nested())
    # 如果要看到task的执行结果
    # 可以使用await等待协程执行完成,并返回结果
    ret = await task
    print(f'nested return {ret}')

asyncio.run(create_task())

# 运行结果
# in nested func
# nested return 13

Примечание. Параллелизм будет подробно объяснен ниже.

3. Future

Futureявляется специальным низкоуровневым (low-level) объект, который является конечным результатом асинхронной операции (eventual result). Когда ожидается объект Future, это означает, что сопрограмма будет продолжать ждать, пока объект Future не будет выполнен в другом месте.

Обычно код прикладного уровня не создается напрямуюFutureобъект. в некоторых библиотеках иasyncioОбъект будет использоваться в модуле.

async def used_future_func():
    await function_that_returns_a_future_object()

0x02 параллелизм

1. Task

мы знаем раньшеTaskмогут выполняться одновременно.asyncio.create_task()Это пакет, который инкапсулирует сопрограмму вTaskМетоды.

async def do_after(what, delay):
    await asyncio.sleep(delay)
    print(what)

# 利用asyncio.create_task创建并行任务
async def corun():
    task1 = asyncio.create_task(do_after('hello', 1)) # 模拟执行1秒的任务
    task2 = asyncio.create_task(do_after('python', 2)) # 模拟执行2秒的任务

    print(f'started at {time.strftime("%X")}')
    # 等待两个任务都完成,两个任务是并行的,所以总时间两个任务中最大的执行时间
    await task1
    await task2

    print(f'finished at {time.strftime("%X")}')

asyncio.run(corun())

# 运行结果
# started at 23:41:08
# hello
# python
# finished at 23:41:10

task1это задача, которая выполняется в течение 1 секунды,task2Это задача, которая выполняется в течение 2 секунд, и две задачи выполняются одновременно, занимая в общей сложности 2 секунды.

2. gather

Помимо использованияasyncio.create_task()также можно использоватьasyncio.gather(), этот метод получает список параметров сопрограммы

async def do_after(what, delay):
    await asyncio.sleep(delay)
    print(what)
    
async def gather():
    print(f'started at {time.strftime("%X")}')
    # 使用gather可将多个协程传入
    await asyncio.gather(
        do_after('hello', 1),
        do_after('python', 2),
    )
    print(f'finished at {time.strftime("%X")}')

asyncio.run(gather())

# 运行结果
# started at 23:47:50
# hello
# python
# finished at 23:47:52

Время, затрачиваемое двумя задачами, — это задача, которая занимает больше всего времени.

ссылка 0x03

  1. docs.Python.org/3/library/… ах…