大数据

python yield于asyncio使用

yield

使用方法

demo01

# yield 像是一个可暂停的return 下次next()从暂停处继续
def my_first_gen():
    print(">>> 启动生成器")
    yield 100
    print(">>> 第一次修复")
    yield 200
    print(">>> 第二次修复")
    yield 300
    print(">>> 生成器彻底结束")

# 注意这里不会打印
g = my_first_gen()
print(f"生成器对象:{g}")

# 逐次驱动
print(next(g))
print(next(g))
print(next(g))
# print(next(g)) # StopIteration

demo02

def my_range(start, end):
    current = start
    while current < end:
        yield current  # 每循环一次 吐出一个数字
        current += 1


for num in my_range(0, 5):
    print(f"当前数字: {num}")

# 验证内存占用(对比列表)
import sys

list_data = [i for i in range(1000000)]
gen_data = my_range(0, 1000000)
print(f"列表内存:{sys.getsizeof(list_data)} 字节")
print(f"生成器内存:{sys.getsizeof(gen_data)} 字节")

# 只要你想流式处理数据(渡日治、分页拉取API) 第一时间想 yield

demo03

def calc_coroutine():
    print("计算器已启动,等待输入...")
    total = 0
    while True:
        # 1. 暂停在这里,等待外部send值进来
        # 2. 外部 send(10) 会把10 赋值给cmd
        cmd = yield total  # yield total将total值返回给调用者 后面 .send()会将send的值赋值给cmd

        if cmd == "reset":
            total = 0
        elif isinstance(cmd, (int, float)):
            total += cmd
        # 如果外部调用close()循环结束

c = calc_coroutine()
# 必须首次执行一个next() 让程序运行到第一个yield处(即等待输入状态)
print(next(c))
print(c.send(10))
print(c.send(5))
print(c.send("reset"))
c.close()

demo04

# yield from 子生成器 当甩手掌柜 我不干了,让子生成器直接跟调用者对话,我是透明的。

def flatten(nested_list):
    for sublist in nested_list:
        # 如果子元素还是列表 递归咱开
        if isinstance(sublist, list):
            # yield from 是直通车 / 传话筒。我不干活,让给子生成器干;
            # 子生成器吐出来的所有东西,我原封不动递给调用者。
            # 遇到子列表,我就原地立正。子生成器生产的所有数据,不经过我手,直接‘瞬移’到调用者面前。
            yield from flatten(sublist)  # 把子生成器的值透传
        else:
            yield sublist


data = [1, [2, 3], [[4, 5], 6]]
for item in flatten(data):
    print(item, end=" ")

asyncio

demo05

# async def 定义协程 await 挂起当前协程去执行别的任务 asyncio.run() 启动事件循环来调度所有任务

import time
import asyncio


# 同步写法 耗时3秒
def sync_task():
    time.sleep(1)  # 死等


start = time.time()
for _ in range(3):
    sync_task()
print(f"同步耗时:{time.time() - start:.2f} 秒")


# 异步写法
async def async_task():
    await asyncio.sleep(1)  # 假装等,实际让出控制权


async def main():
    # 创建3个任务 并发执行
    tasks = [async_task() for _ in range(3)]
    await asyncio.gather(*tasks)


start = time.time()
asyncio.run(main())
print(f"异步并发耗时:{time.time() - start:.2f} 秒")

demo06

import asyncio

# 协程不 await,就像汽车不发动
async def say_hello():
    print("Hello")
    return "World"


async def main():
    # 错误:直接调到用say_hello()返回的是协程对象,不会执行打印
    coro = say_hello()
    print(f"类型:{coro}")

    # 正确: 必须await 才能执行
    result = await say_hello()
    print(f"返回值:{result}")


asyncio.run(main())

demo07

import asyncio


async def worker(name, delay, fail=False):
    print(f"Worker {name} 启动,等待 {delay}s")
    await asyncio.sleep(delay)
    if fail:
        raise ValueError(f"Worker {name} 爆炸了")
    return f"Worker {name} 的结果"


async def main():
    try:
        async with asyncio.TaskGroup() as tg:
            t1 = tg.create_task(worker("A", 1))
            t2 = tg.create_task(worker("B", 0.5, fail=True))  # 这个会报错
            t3 = tg.create_task(worker("C", 2))
        # 如果t2报错, t1和t3会被自动取消,不会默认后台运行
        print([t.result() for t in [t1, t2, t3]])
    except ValueError as e:
        print(f"捕获到任务组异常:{e}")


asyncio.run(main())

demo08

import asyncio


async def log_stream():
    for i in range(5):
        # 模拟从远程kafka或websocket拉取数据,耗时1秒
        await asyncio.sleep(1)
        yield f"日志条目 #{i} 时间:{asyncio.get_running_loop().time():.2f}"


# async for 专门用来遍历异步生成器 每次循环遇到await会让出控制权,非常适合流式数据接口

async def consumer():
    # 使用async for 迭代异步生成器
    async for log in log_stream():
        print(f"处理:{log}")
        # 可以在这里调用另一个异步函数保存到数据库


asyncio.run(consumer())

demo09

import asyncio
import random


# 模拟异步HTTP请求
async def mock_api_request(url_id):
    delay = random.uniform(0.5, 1.5)  # 模拟网络延迟
    await asyncio.sleep(delay)
    return f"Response from API-{url_id} (耗时 {delay:.2f})s"


# 异步生成器:产生并发请求的结果(按完成顺序)
async def fetch_all(url_ids):
    async with asyncio.TaskGroup() as tg:
        # 创建所有请求任务
        tasks = {tg.create_task(mock_api_request(id)): id for id in url_ids}

        # 核心技巧:等待每个任务完成,完成后 yield 结果
        # 因为 TaskGroup 不能直接 yield,我们配合 asyncio.as_completed 来做
        # 但 as_completed 不适用 TaskGroup 内部。改进写法:
        pass  # 为了教学清晰,我们改用更直接的 as_completed(不依赖 TaskGroup)


# 更推荐的纯净写法(放弃 TaskGroup 的强制绑定,使用 as_completed)
async def fetch_all_v2(url_ids):
    tasks = [asyncio.create_task(mock_api_request(id)) for id in url_ids]
    # as_completed 返回迭代器,每完成一个任务就产出
    for coro in asyncio.as_completed(tasks):
        result = await coro
        yield result


async def main():
    print("开始并发抓取...")
    # 异步迭代,只要任何一个 API 返回,立刻打印,不用等全部完成
    async for data in fetch_all_v2([1, 2, 3, 4, 5]):
        print(f">>> 收到数据:{data}")
    print("所有数据接受完毕!")


if __name__ == '__main__':
    asyncio.run(main())