You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Python生成器函数调用时立即抛出异常而非延迟触发

问题描述

我有一个用于包装包含多个变量和属性的数据文件的Python类,部分变量自身也带有属性,我希望通过生成器遍历这些属性。当指定变量不存在于文件中时,我希望立即抛出Exception;若变量存在则生成对应的属性。但由于Python生成器采用惰性求值,异常仅在首次迭代生成器时才会抛出,而非调用生成器函数时。示例代码如下:

from typing import List, Dict, Any

class file:
    
    def __init__(self, varlist: List[str], attrlist: Dict[str, Any]):
        self.varlist = varlist
        self.attrlist = attrlist

    def loadvar(self, varname: str):
        ...

    def attrs(self, varname: str = None):
        if varname:
            if varname not in self.varlist:
                raise RuntimeError(f"{varname} is not a valid variable name")
            yield from self.loadvar(varname).attrs()
        yield from self.attrlist.keys()

datafile = file(['a', 'b','c'], {'d':1, 'e':2, 'f':3})
list(datafile.attrs('non-existent')) # <-- 抛出异常;符合预期
attr = datafile.attrs('non-existent') # <-- 不抛出异常;不符合预期
list(attr)  # <-- 此时才抛出异常

核心问题:是否可以实现让异常在生成器函数调用时抛出,而非迭代时?

解决方案

可以实现,核心思路是将参数校验逻辑提前到生成器对象创建前执行——因为生成器函数调用时仅返回对象,不会执行内部代码,直到首次迭代。以下是两种可行方案:

方案一:拆分校验与生成器逻辑

把参数检查放在单独的前置逻辑中,校验通过后再返回生成器,这样调用attrs()时会立即执行校验:

from typing import List, Dict, Any

class File:  # 遵循PEP8规范,类名采用大驼峰命名
    def __init__(self, varlist: List[str], attrlist: Dict[str, Any]):
        self.varlist = varlist
        self.attrlist = attrlist

    def loadvar(self, varname: str):
        # 模拟返回带attrs方法的变量对象
        class MockVariable:
            def attrs(self):
                yield from ['var_attr1', 'var_attr2']
        return MockVariable()

    def _attrs_generator(self, varname: str = None):
        # 纯生成器逻辑,不包含参数校验
        if varname:
            yield from self.loadvar(varname).attrs()
        yield from self.attrlist.keys()

    def attrs(self, varname: str = None):
        # 调用时立即执行参数校验
        if varname and varname not in self.varlist:
            raise RuntimeError(f"{varname} is not a valid variable name")
        # 校验通过后返回生成器
        return self._attrs_generator(varname)

# 测试验证
datafile = File(['a', 'b','c'], {'d':1, 'e':2, 'f':3})
attr = datafile.attrs('non-existent')  # 调用时直接抛出异常,符合预期

方案二:用装饰器触发前置校验

如果不想拆分函数,可以通过装饰器在生成器创建后立即触发一次迭代,执行校验逻辑:

from typing import List, Dict, Any
from functools import wraps

def validate_on_call(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        gen = func(*args, **kwargs)
        try:
            # 触发一次迭代,执行到第一个yield,此时校验逻辑已完成
            first_item = next(gen)
            # 重新组合生成器,确保所有元素都能被遍历
            yield first_item
            yield from gen
        except StopIteration:
            # 处理生成器无元素的情况
            return
    return wrapper

class File:
    def __init__(self, varlist: List[str], attrlist: Dict[str, Any]):
        self.varlist = varlist
        self.attrlist = attrlist

    def loadvar(self, varname: str):
        class MockVariable:
            def attrs(self):
                yield from ['var_attr1', 'var_attr2']
        return MockVariable()

    @validate_on_call
    def attrs(self, varname: str = None):
        if varname:
            if varname not in self.varlist:
                raise RuntimeError(f"{varname} is not a valid variable name")
            yield from self.loadvar(varname).attrs()
        yield from self.attrlist.keys()

# 测试验证
datafile = File(['a', 'b','c'], {'d':1, 'e':2, 'f':3})
attr = datafile.attrs('non-existent')  # 调用时立即抛出异常,符合预期

原理说明

Python生成器的惰性求值特性决定了:调用生成器函数时仅返回生成器对象,不会执行函数内部代码,直到第一次调用next()或开始迭代。因此要让异常在调用时抛出,必须在生成器对象可用前完成参数校验,或者创建对象后立即触发一次迭代来执行校验逻辑。

方案一的可读性和维护性更优,推荐使用这种拆分逻辑的方式,明确区分参数校验与迭代生成的职责。

内容的提问来源于stack exchange,提问作者MrAzzaman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 04:42:12