Python迭代类__next__未触发,返回生成器对象问题排查
迭代器类返回生成器而非预期numpy数组的问题解决
问题现象
自定义可迭代类VSIFile用于遍历VSI文件的ROI区域,实现了标准的__iter__(返回自身)和__next__方法,但执行遍历代码时,输出的是生成器对象而非预期的numpy数组。调试确认__next__方法从未被触发,仅__iter__方法执行。测试简单数字计数迭代器类运行正常,但当前类出现异常,示例输出:
<generator object VSIFile.__next__ at 0x000001AA3C4AFC80>
关联代码
vsi_file.py
from typing import Tuple import javabridge import bioformats from tqdm import tqdm from cv2 import resize javabridge.start_vm(class_path=bioformats.JARS) class VSIFile: def __init__(self, vsi_file: str, roi_size: Tuple[int, int] = (1024, 1024), target_size: Tuple[int, int] = (256, 256), use_pbar: bool = True): self.file_path = vsi_file self.roi_size = roi_size self.target_size = target_size self.slide = None self.shape = None self.max_x_idx = None self.max_y_idx = None self.num_rois = None self.skip = [1, 2, 5, 11, 22, 45, 72] if use_pbar: self.pbar = tqdm() else: self.pbar = None def __enter__(self): self.slide = bioformats.ImageReader(self.file_path) self.shape = self.slide.rdr.getSizeY(), self.slide.rdr.getSizeX(), 3 self.max_x_idx = self.shape[1] // self.roi_size[1] self.max_y_idx = self.shape[0] // self.roi_size[0] self.num_rois = self.max_x_idx * self.max_y_idx if self.pbar is not None: self.pbar.total = self.num_rois self.pbar.refresh() return self def __exit__(self, exc_type, exc_val, exc_tb): self.slide.close() if self.pbar is not None: self.pbar.close() def __del__(self): self.slide.close() if self.pbar is not None: self.pbar.close() def __iter__(self): return self def __next__(self): while self.idx in self.skip: if self.idx == self.max_x_idx * self.max_y_idx: if self.pbar is not None: self.pbar.close() raise StopIteration self.idx += 1 if self.pbar is not None: self.pbar.update(1) if self.idx == self.max_x_idx * self.max_y_idx: if self.pbar is not None: self.pbar.close() raise StopIteration y = (self.idx // self.max_x_idx) * self.roi_size[0] x = (self.idx % self.max_x_idx) * self.roi_size[1] roi = self.get_roi(x, y, self.roi_size[0], self.roi_size[1]) roi = resize(roi, self.target_size) if self.target_size else roi yield roi self.idx += 1 if self.pbar is not None: self.pbar.update(1)
process_vsi.py(相关片段)
with VSIFile(os.path.abspath(os.path.join(data_dir, file))) as vsi: for roi in vsi: print(roi)
问题根源与修复方案
核心问题1:__next__误用yield
迭代器的__next__方法要求直接返回单个元素,当使用yield时,该方法会变成生成器函数,调用时直接返回生成器对象,而非预期的numpy数组,这也是遍历得到生成器的原因。
核心问题2:未初始化迭代索引self.idx
代码中使用了self.idx但未在任何地方初始化,会导致运行时抛出AttributeError,需要在__enter__方法中添加初始化语句。
修复后的关键代码
- 在
__enter__方法末尾添加索引初始化:
def __enter__(self): # 原有代码... self.idx = 0 # 添加这行初始化迭代索引 return self
- 修改
__next__方法,移除yield,改为直接返回roi,并调整后续逻辑:
def __next__(self): while self.idx in self.skip: if self.idx == self.max_x_idx * self.max_y_idx: if self.pbar is not None: self.pbar.close() raise StopIteration self.idx += 1 if self.pbar is not None: self.pbar.update(1) if self.idx == self.max_x_idx * self.max_y_idx: if self.pbar is not None: self.pbar.close() raise StopIteration y = (self.idx // self.max_x_idx) * self.roi_size[0] x = (self.idx % self.max_x_idx) * self.roi_size[1] roi = self.get_roi(x, y, self.roi_size[0], self.roi_size[1]) roi = resize(roi, self.target_size) if self.target_size else roi self.idx += 1 if self.pbar is not None: self.pbar.update(1) return roi # 替换yield为return
验证
修复后再次执行process_vsi.py的遍历代码,将输出预期的numpy数组,__next__方法会被正常触发,完成ROI的迭代输出。
内容的提问来源于stack exchange,提问作者Jack Avante
相关产品推荐
相关产品推荐

