通用的批处理 Wrapper。 在静态检查/IDE 里,BatchWrapper.run 的签名和 operator.run 完全一致。 运行时,会把 operator.run 的 __doc__ 和 __signature__ 也拷过来, 这样 help(bw.run) 时能看到原 operator 的文档。
| 18 | ... |
| 19 | |
| 20 | class BatchWrapper(Generic[P, R]): |
| 21 | """ |
| 22 | 通用的批处理 Wrapper。 |
| 23 | |
| 24 | 在静态检查/IDE 里,BatchWrapper.run 的签名和 operator.run 完全一致。 |
| 25 | 运行时,会把 operator.run 的 __doc__ 和 __signature__ 也拷过来, |
| 26 | 这样 help(bw.run) 时能看到原 operator 的文档。 |
| 27 | """ |
| 28 | def __init__(self, operator: HasRun[P, R], start_batch: int = 0, batch_size: int = 32, batch_cache: bool = False) -> None: |
| 29 | self._operator = operator |
| 30 | self._logger = get_logger() |
| 31 | self._batch_size = batch_size |
| 32 | self._batch_cache = batch_cache |
| 33 | self.start_batch = start_batch |
| 34 | |
| 35 | # 动态拷贝 operator.run 的 __doc__ 和 inspect.signature |
| 36 | orig = operator.run |
| 37 | sig = inspect.signature(orig) |
| 38 | # wrapped = wraps(orig)(self.run) # 先 wrap docstring, __name__… |
| 39 | # wrapped.__signature__ = sig # 再贴上准确的 signature |
| 40 | # 把它绑到实例上覆盖掉 class method,这样 help(instance.run) 能看到原文档 |
| 41 | # object.__setattr__(self, "run", wrapped) |
| 42 | |
| 43 | def run(self, *args: P.args, **kwargs: P.kwargs) -> List[R]: |
| 44 | # —— 1. 提取 storage —— |
| 45 | # only support FileStorage for now |
| 46 | if args: |
| 47 | storage: FileStorage = args[0] # type: ignore[assignment] |
| 48 | rest_args = args[1:] |
| 49 | rest_kwargs = kwargs |
| 50 | else: |
| 51 | storage: FileStorage = kwargs.get("storage") # type: ignore[assignment] |
| 52 | if storage is None: |
| 53 | raise ValueError( |
| 54 | f"A DataFlowStorage is required for {self._operator!r}.run()" |
| 55 | ) |
| 56 | rest_kwargs = {k: v for k, v in kwargs.items() if k != "storage"} |
| 57 | rest_args = () |
| 58 | |
| 59 | # prepare a dummy storage,for batch processing |
| 60 | if self._batch_cache: # if we need to cache each batch result |
| 61 | self._dummy_storage = DummyStorage( |
| 62 | cache_path=storage.cache_path, |
| 63 | file_name_prefix=storage.file_name_prefix, |
| 64 | cache_type=storage.cache_type |
| 65 | ) |
| 66 | else: # if we don't need to cache each batch result |
| 67 | self._dummy_storage = DummyStorage() |
| 68 | |
| 69 | # —— 2. 读出全量数据并按 batch_size 切分 —— |
| 70 | whole_dataframe = storage.read() |
| 71 | num_batches = (len(whole_dataframe) + self._batch_size - 1) // self._batch_size # Calculate number of batches |
| 72 | |
| 73 | output_dataframe = pd.DataFrame() |
| 74 | self._logger.info(f"Total {len(whole_dataframe)} items, will process in {num_batches} batches of size {self._batch_size}.") |
| 75 | # for batch_num in tqdm(range(num_batches)): |
| 76 | for batch_num in tqdm(range(self.start_batch, num_batches)): # 手动设置断点 |
| 77 | start_index = batch_num * self._batch_size |