python生成器与协程深度剖析

部署运行你感兴趣的模型镜像

目录

生成器

传统列表 vs 生成器对比

yield机制深度解析

生成器的高级用法

协程的演进:从yield到async/await

基于yield的协程

现代async/await语法

协程的错误处理和超时控制

异步生成器与异步迭代器

异步生成器

异步迭代器实现

实战案例:异步爬虫框架设计


生成器

生成器是一种特殊的迭代器,它可以在需要时才产生值,而不是一次性将所有值加载到内存中。

传统列表 vs 生成器对比

def memory_comparison():
    import sys
    
    # 列表推导式 - 占用大量内存
    list_comp = [x**2 for x in range(1000000)]
    print(f"列表占用内存: {sys.getsizeof(list_comp)} bytes")
    
    # 生成器表达式 - 内存友好
    gen_exp = (x**2 for x in range(1000000))
    print(f"生成器占用内存: {sys.getsizeof(gen_exp)} bytes")
    
    return list_comp, gen_exp

# 运行对比
list_data, gen_data = memory_comparison()

列表推导式

代码中列表推导式一次性生成了100万个元素的平方,并将所有结果存储在内存中sys.getsizeof函数显示列表对象占用的内存大小,通常会非常大因为它包含了所有元素的实际数据。

生成器表达式

生成器表达式是惰性求值的,它不会一次性生成所有元素,而是按需计算每个元素sys.getsizeof显示的内存占用非常小,仅为生成器对象本身的开销,不包含元素数据。

yield机制深度解析

yield关键字是生成器的核心,它不仅可以产生值,还能接收值,实现双向通信。

def advanced_generator():
    """演示yield的双向通信能力"""
    print("生成器启动")
    value = None
    while True:
        # yield既可以发送值,也可以接收值
        received = yield value
        
        if received is None:
            value = "默认值"
        elif received == "stop":
            break
        else:
            value = f"处理后的值: {received}"
    print("生成器结束")

def test_bidirectional_generator():
    gen = advanced_generator()
    
    # 启动生成器,执行到第一个yield,输出初始值None
    print(next(gen))  # 输出: None
    
    # 发送值给生成器,接收处理后的返回值
    print(gen.send("hello"))  # 输出: 处理后的值: hello
    print(gen.send("world"))  # 输出: 处理后的值: world
    
    # 发送停止信号,结束生成器
    try:
        gen.send("stop")
    except StopIteration:
        print("生成器已停止")

test_bidirectional_generator()

调用advanced_generator()返回一个生成器对象,但函数体并未执行。使用next(gen)启动生成器,执行到第一个yield表达式暂停,返回yield后面的值(此处为None)。生成器此时处于暂停状态,等待外部发送值。

yield能接收外部通过send()方法传入的值。在received = yield value中,yield valuevalue发送给调用者,同时暂停生成器。当调用者使用gen.send(some_value)时,some_value被赋值给received,生成器继续执行。

生成器的高级用法


def fibonacci_generator(n):
    """斐波那契数列生成器"""
    a, b = 0, 1
    count = 0
    while count < n:
        yield a
        a, b = b, a + b
        count += 1

def pipeline_example():
    """生成器管道处理示例"""
    
    def read_data():
        """模拟数据源"""
        for i in range(100):
            yield f"data_{i}"
    
    def filter_data(data_stream):
        """过滤数据"""
        for item in data_stream:
            if int(item.split('_')[1]) % 2 == 0:
                yield item
    
    def transform_data(data_stream):
        """转换数据"""
        for item in data_stream:
            yield item.upper()
    
    def process_data(data_stream):
        """处理数据"""
        for item in data_stream:
            yield f"processed_{item}"
    
    # 构建处理管道
    pipeline = process_data(
        transform_data(
            filter_data(
                read_data()
            )
        )
    )
    
    # 处理前10个结果
    for i, result in enumerate(pipeline):
        if i >= 10:
            break
        print(result)

pipeline_example()

生成器通过yield实现惰性求值,适合处理大数据和流式数据

而模拟的数据处理管道,包含数据读取、过滤、转换和最终处理四个阶段。每个阶段都是一个生成器函数,接收上游生成器作为输入,逐条处理数据并 yield 结果。通过嵌套调用,实现了数据的流式处理,避免了中间数据的全部存储,极大节省内存。最终打印处理后的前10条数据,展示了管道的效果。

协程的演进:从yield到async/await

基于yield的协程

协程(Coroutine)是一种比线程更轻量级的“用户态”并发技术。它允许程序在执行过程中主动暂停(挂起)和恢复,从而实现多任务的切换和并发执行。与线程不同协程的切换由程序控制,不依赖操作系统调度,因此开销更小效率更高。在async/await语法出现之前,Python使用yield来实现协程。

def old_style_coroutine():
    """传统yield协程示例"""
    print("协程启动")
    
    while True:
        try:
            value = yield
            print(f"接收到值: {value}")
            
            # 模拟异步操作
            import time
            time.sleep(0.1)
            print(f"处理完成: {value}")
            
        except GeneratorExit:
            print("协程关闭")
            break

def coroutine_scheduler():
    """简单的协程调度器"""
    coroutines = []
    
    # 创建多个协程
    for i in range(3):
        coro = old_style_coroutine()
        next(coro)  # 启动协程
        coroutines.append(coro)
    
    # 轮询执行协程
    for i in range(5):
        for j, coro in enumerate(coroutines):
            try:
                coro.send(f"task_{j}_step_{i}")
            except StopIteration:
                pass
    
    # 关闭所有协程
    for coro in coroutines:
        coro.close()
coroutine_scheduler()

yield协程实现:利用yield表达式实现函数的暂停与恢复。通过value = yield接收外部传入的数据,实现协程与调用者的双向通信。使用GeneratorExit异常捕获协程关闭信号,优雅释放资源。

协程调度器设计:创建多个协程实例,并通过next()启动,执行到第一个yield暂停。通过循环轮询,依次向每个协程发送任务数据,实现简单的“协作式多任务”调度。调度器负责驱动协程执行,模拟异步任务的并发处理。最后调用close()关闭所有协程,确保协程资源正确释放。

模拟异步操作:使用time.sleep(0.1)模拟耗时操作,展示协程在处理任务时的暂停与恢复。传统yield协程本身是同步阻塞的,真正的异步需要结合事件循环或异步库。

现代async/await语法

import asyncio
import time

async def modern_coroutine(name, delay):
    """现代async/await协程"""
    print(f"协程 {name} 开始执行")
    
    # 异步等待
    await asyncio.sleep(delay)
    
    print(f"协程 {name} 执行完成,耗时 {delay} 秒")
    return f"结果来自 {name}"

async def coroutine_orchestration():
    """协程编排示例"""
    
    # 并发执行多个协程
    tasks = [
        modern_coroutine("任务1", 1),
        modern_coroutine("任务2", 2),
        modern_coroutine("任务3", 0.5)
    ]
    
    start_time = time.time()
    
    # 等待所有任务完成
    results = await asyncio.gather(*tasks)
    
    end_time = time.time()
    
    print(f"所有任务完成,总耗时: {end_time - start_time:.2f} 秒")
    print("结果:", results)

# 运行协程
asyncio.run(coroutine_orchestration())

现代async/await协程特点:使用async def定义异步函数,返回协程对象。通过await挂起协程,等待异步操作完成(如asyncio.sleep),期间事件循环调度其他任务执行。利用asyncio.gather实现多个协程的并发执行,极大提升I/O密集型任务效率。事件循环(asyncio.run)负责调度和管理协程生命周期。协程函数可以直接返回结果,方便后续处理。

协程的错误处理和超时控制

import asyncio
async def robust_coroutine_example():
    """健壮的协程示例,包含错误处理和超时控制"""
    
    async def risky_operation(should_fail=False):
        await asyncio.sleep(1)
        if should_fail:
            raise ValueError("模拟错误")
        return "操作成功"
    
    async def timeout_operation():
        await asyncio.sleep(5)  # 模拟长时间操作
        return "超时操作完成"
    
    # 错误处理
    try:
        result = await risky_operation(should_fail=True)
        print(result)
    except ValueError as e:
        print(f"捕获错误: {e}")
    
    # 超时控制
    try:
        result = await asyncio.wait_for(timeout_operation(), timeout=2.0)
        print(result)
    except asyncio.TimeoutError:
        print("操作超时")
    
    # 任务取消
    task = asyncio.create_task(timeout_operation())
    await asyncio.sleep(1)
    task.cancel()
    
    try:
        await task
    except asyncio.CancelledError:
        print("任务被取消")

asyncio.run(robust_coroutine_example())

错误处理:使用try-except捕获协程中抛出的异常,保证程序不会因未处理异常崩溃。示例中risky_operation模拟可能失败的异步操作,抛出ValueError,调用处捕获并处理。

超时控制:利用asyncio.wait_for为协程设置超时时间超过指定时间未完成则抛出asyncio.TimeoutError。通过捕获超时异常,可以优雅地处理长时间未响应的异步任务。

任务取消:使用asyncio.create_task创建独立任务,允许在运行中主动取消。调用task.cancel()发送取消请求,协程内部抛出asyncio.CancelledError异常。通过捕获CancelledError,可以执行清理操作或记录日志。

异步生成器与异步迭代器

异步生成器

import asyncio
import aiofiles

async def async_file_reader(filename):
    """异步文件读取生成器"""
    try:
        async with aiofiles.open(filename, 'r') as file:
            async for line in file:
                # 模拟处理时间
                await asyncio.sleep(0.01)
                yield line.strip()
    except FileNotFoundError:
        print(f"文件 {filename} 不存在")

async def async_number_generator(start, end, delay=0.1):
    """异步数字生成器"""
    for i in range(start, end):
        await asyncio.sleep(delay)
        yield i

async def test_async_generators():
    """测试异步生成器"""
    
    # 测试异步数字生成器
    print("异步数字生成器:")
    async for num in async_number_generator(1, 6, 0.2):
        print(f"生成数字: {num}")
    
    # 创建测试文件
    with open("test.txt", "w") as f:
        f.write("line 1\nline 2\nline 3\n")
    
    # 测试异步文件读取
    print("\n异步文件读取:")
    async for line in async_file_reader("test.txt"):
        print(f"读取行: {line}")

asyncio.run(test_async_generators())

异步生成器基础:使用async def定义异步生成器函数,结合yield关键字实现异步迭代。通过async for语法异步遍历生成器,支持非阻塞式数据消费。

异步数字生成器:async_number_generator通过异步延迟模拟异步数据产生过程。适合模拟实时数据流或需要异步等待的数字序列生成。

异步文件读取生成器:利用aiofiles库实现异步文件操作,避免阻塞事件循环。通过async for逐行读取文件内容,结合await asyncio.sleep模拟处理延迟。具备异常处理机制,捕获文件不存在等错误,增强健壮性。

异步迭代器实现

import asyncio
class AsyncRange:
    """异步范围迭代器"""
    
    def __init__(self, start, end, delay=0.1):
        self.start = start
        self.end = end
        self.delay = delay
        self.current = start
    
    def __aiter__(self):
        return self
    
    async def __anext__(self):
        if self.current >= self.end:
            raise StopAsyncIteration
        
        await asyncio.sleep(self.delay)
        value = self.current
        self.current += 1
        return value

class AsyncDataProcessor:
    """异步数据处理器"""
    
    def __init__(self, data_source):
        self.data_source = data_source
    
    def __aiter__(self):
        return self
    
    async def __anext__(self):
        try:
            data = await self.data_source.__anext__()
            # 模拟数据处理
            await asyncio.sleep(0.05)
            return f"processed_{data}"
        except StopAsyncIteration:
            raise

async def test_async_iterators():
    # 测试AsyncRange
    print("AsyncRange迭代器:")
    async for value in AsyncRange(1, 5, 0.2):
        print(f"值: {value}")
    
    # 测试AsyncDataProcessor
    print("\nAsyncDataProcessor:")
    data_source = AsyncRange(10, 15, 0.1)
    processor = AsyncDataProcessor(data_source)
    
    async for processed_data in processor:
        print(f"处理结果: {processed_data}")
asyncio.run(test_async_iterators())

异步迭代器协议:自定义异步迭代器需实现__aiter__()返回自身或异步迭代器对象。实现__anext__()异步方法,返回下一个元素或抛出StopAsyncIteration结束迭代。通过async for语法自动调用__aiter__()和__anext__(),实现异步遍历。

AsyncRange类:模拟同步range的异步版本,支持异步延迟控制。每次调用__anext__()时,异步等待指定delay后返回当前值。迭代结束时抛出StopAsyncIteration,通知async for循环终止。

AsyncDataProcessor类:接收一个异步迭代器作为数据源,内部通过调用数据源的__anext__()获取数据。对获取的数据进行异步处理(模拟延迟),返回处理后的结果。通过捕获StopAsyncIteration实现迭代终止的传递。

异步组合与链式处理:AsyncDataProcessor将异步迭代器作为输入,实现了异步数据流的链式处理。这种设计模式适合构建复杂的异步数据处理管道,支持灵活组合和扩展。

实战案例:异步爬虫框架设计

核心code

import asyncio
import aiohttp
import time
from urllib.parse import urljoin, urlparse
from collections import deque
import logging

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class AsyncSpider:
    """异步爬虫框架核心类"""
    
    def __init__(self, max_concurrent=10, delay=1.0):
        self.max_concurrent = max_concurrent
        self.delay = delay
        self.session = None
        self.semaphore = asyncio.Semaphore(max_concurrent)
        self.visited_urls = set()
        self.failed_urls = set()
        
    async def __aenter__(self):
        """异步上下文管理器入口"""
        connector = aiohttp.TCPConnector(limit=100, limit_per_host=30)
        timeout = aiohttp.ClientTimeout(total=30)
        self.session = aiohttp.ClientSession(
            connector=connector,
            timeout=timeout,
            headers={'User-Agent': 'AsyncSpider/1.0'}
        )
        return self
    
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        """异步上下文管理器出口"""
        if self.session:
            await self.session.close()
    
    async def fetch(self, url):
        """异步获取单个URL"""
        async with self.semaphore:
            try:
                await asyncio.sleep(self.delay)
                
                async with self.session.get(url) as response:
                    if response.status == 200:
                        content = await response.text()
                        self.visited_urls.add(url)
                        logger.info(f"成功获取: {url}")
                        return {
                            'url': url,
                            'status': response.status,
                            'content': content,
                            'headers': dict(response.headers)
                        }
                    else:
                        logger.warning(f"HTTP错误 {response.status}: {url}")
                        self.failed_urls.add(url)
                        return None
                        
            except Exception as e:
                logger.error(f"获取失败 {url}: {str(e)}")
                self.failed_urls.add(url)
                return None
    
    async def fetch_multiple(self, urls):
        """异步获取多个URL"""
        tasks = [self.fetch(url) for url in urls]
        results = await asyncio.gather(*tasks, return_exceptions=True)
        
        # 过滤掉异常和None结果
        valid_results = [r for r in results if r is not None and not isinstance(r, Exception)]
        return valid_results

class AsyncCrawler:
    """异步爬虫调度器"""
    
    def __init__(self, spider: AsyncSpider):
        self.spider = spider
        self.url_queue = deque()
        self.results = []
        
    def add_urls(self, urls):
        """添加URL到队列"""
        for url in urls:
            if url not in self.spider.visited_urls:
                self.url_queue.append(url)
    
    async def crawl_batch(self, batch_size=50):
        """批量爬取URL"""
        batch_urls = []
        
        for _ in range(min(batch_size, len(self.url_queue))):
            if self.url_queue:
                batch_urls.append(self.url_queue.popleft())
        
        if batch_urls:
            results = await self.spider.fetch_multiple(batch_urls)
            self.results.extend(results)
            return results
        
        return []
    
    async def crawl_all(self, batch_size=50):
        """爬取所有队列中的URL"""
        while self.url_queue:
            batch_results = await self.crawl_batch(batch_size)
            logger.info(f"完成批次,获取 {len(batch_results)} 个结果")
            
            # 可以在这里添加结果处理逻辑
            yield batch_results

class DataProcessor:
    """数据处理器"""
    
    @staticmethod
    async def extract_links(html_content, base_url):
        """从HTML中提取链接"""
        # 简化的链接提取(实际项目中建议使用BeautifulSoup或lxml)
        import re
        
        link_pattern = r'href=[\'"]([^\'"]+)[\'"]'
        links = re.findall(link_pattern, html_content)
        
        absolute_links = []
        for link in links:
            absolute_url = urljoin(base_url, link)
            if urlparse(absolute_url).netloc:  # 只保留完整URL
                absolute_links.append(absolute_url)
        
        return absolute_links
    
    @staticmethod
    async def save_results(results, filename):
        """异步保存结果到文件"""
        import aiofiles
        import json
        
        async with aiofiles.open(filename, 'w', encoding='utf-8') as f:
            for result in results:
                await f.write(json.dumps(result, ensure_ascii=False) + '\n')
async def spider_example():
    """爬虫使用示例"""
    
    # 测试URL列表
    test_urls = [
        'https://httpbin.org/delay/1',
        'https://httpbin.org/json',
        'https://httpbin.org/html',
        'https://httpbin.org/xml',
        'https://httpbin.org/status/200'
    ]
    
    start_time = time.time()
    
    async with AsyncSpider(max_concurrent=5, delay=0.5) as spider:
        crawler = AsyncCrawler(spider)
        crawler.add_urls(test_urls)
        
        all_results = []
        
        # 使用异步生成器处理结果
        async for batch_results in crawler.crawl_all(batch_size=3):
            all_results.extend(batch_results)
            
            # 实时处理每批结果
            for result in batch_results:
                print(f"处理结果: {result['url']} - 状态: {result['status']}")
        
        # 保存结果
        processor = DataProcessor()
        await processor.save_results(all_results, 'spider_results.json')
        
        end_time = time.time()
        
        print(f"\n爬取完成!")
        print(f"总耗时: {end_time - start_time:.2f} 秒")
        print(f"成功: {len(spider.visited_urls)} 个URL")
        print(f"失败: {len(spider.failed_urls)} 个URL")
        print(f"总结果: {len(all_results)} 条")

# 运行爬虫示例
asyncio.run(spider_example())
# 高级特性:递归爬取和去重
class RecursiveCrawler(AsyncCrawler):
    """递归爬虫,支持深度爬取"""
    
    def __init__(self, spider: AsyncSpider, max_depth=2, domain_filter=None):
        super().__init__(spider)
        self.max_depth = max_depth
        self.domain_filter = domain_filter
        self.url_depth = {}
    
    def add_url_with_depth(self, url, depth=0):
        """添加URL并记录深度"""
        if (url not in self.spider.visited_urls and 
            url not in self.url_depth and 
            depth <= self.max_depth):
            
            if self.domain_filter:
                if urlparse(url).netloc not in self.domain_filter:
                    return
            
            self.url_queue.append(url)
            self.url_depth[url] = depth
    
    async def recursive_crawl(self):
        """递归爬取"""
        while self.url_queue:
            current_url = self.url_queue.popleft()
            current_depth = self.url_depth.get(current_url, 0)
            
            # 爬取当前URL
            result = await self.spider.fetch(current_url)
            
            if result and current_depth < self.max_depth:
                # 提取新链接
                processor = DataProcessor()
                new_links = await processor.extract_links(
                    result['content'], 
                    current_url
                )
                
                # 添加新链接到队列
                for link in new_links[:5]:  # 限制每页最多5个链接
                    self.add_url_with_depth(link, current_depth + 1)
                
                logger.info(f"从 {current_url} 提取到 {len(new_links)} 个链接")
            
            yield result

async def recursive_spider_example():
    """递归爬虫示例"""
    
    async with AsyncSpider(max_concurrent=3, delay=1.0) as spider:
        crawler = RecursiveCrawler(
            spider, 
            max_depth=2, 
            domain_filter=['httpbin.org']
        )
        
        # 添加起始URL
        crawler.add_url_with_depth('https://httpbin.org/links/5')
        
        results = []
        async for result in crawler.recursive_crawl():
            if result:
                results.append(result)
                print(f"爬取: {result['url']}")
        
        print(f"递归爬取完成,共获取 {len(results)} 个页面")

# 运行递归爬虫
asyncio.run(recursive_spider_example())

AsyncSpider — 异步HTTP请求核心: 利用aiohttp.ClientSession实现高效异步HTTP请求。
通过asyncio.Semaphore限制最大并发数,防止过载目标服务器。支持请求延迟控制,避免请求过于频繁导致封禁。具备完善的异常捕获和状态码判断,自动记录成功与失败的URL。提供fetch单个请求和fetch_multiple批量请求接口,支持并发任务调度。

AsyncCrawler — 异步爬虫调度器:维护URL队列(deque),支持动态添加待爬取URL。
通过crawl_batch实现批量异步爬取,结合fetch_multiple高效获取数据。采用异步生成器crawl_all逐批返回爬取结果,方便实时处理和流式消费。设计灵活,易于扩展为多层爬取或深度爬取。

DataProcessor — 数据处理与持久化:提供简易的HTML链接提取方法,利用正则表达式解析href属性,结合urljoin生成绝对URL。采用aiofiles实现异步文件写入,保证I/O操作非阻塞。支持JSON格式逐条写入,方便后续数据分析和复用。

异步上下文管理与资源释放:AsyncSpider实现异步上下文管理器接口__aenter__和__aexit__,确保aiohttp.ClientSession正确创建与关闭,避免资源泄漏。

递归爬虫优势:自动发现新链接,实现多层级网页数据采集。深度控制避免爬取过深导致资源耗尽。域名过滤防止爬虫跑出目标站点。异步生成器设计支持流式处理,提升效率。

您可能感兴趣的与本文相关的镜像

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值