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

如何重新打开已关闭的io.BytesIO对象?解决FTP上传报错

问题:如何重新使用已关闭的io.BytesIO对象?

我有两段代码:一段用于在内存中生成CSV文件,另一段用于将该文件发送至FTP服务器。

内存生成CSV的代码

def to_csv(events: list) -> io.BytesIO():
    if not events:
        return None
    bio = io.BytesIO()
    iow = io.TextIOWrapper(bio)
    writer = csv.DictWriter(iow, fieldnames=events[0].keys())
    writer.writeheader()
    writer.writerows(events)
    iow.flush()
    bio.seek(0)
    return bio

FTP发送代码

def send_data(self, bytes: io.BytesIO) -> str:
    filename = f"{time.time()}.csv"
    if not bytes:
        self.__logger.warning("No data to send")
        return None
    try:
        self.__ftp.storbinary(f"STOR {filename}", bytes)
    except ftp.all_errors as e:
        self.__logger.error(
            "FTP fail data send",
            extra={
                "host": self.__cfg.ftp.host,
                "type": type(e).__name__,
                "line": e.__traceback__.tb_lineno,
                "file": __file__,
                "detail": e,
            },
        )
        return None

现在传入send_data的io.BytesIO对象已关闭,调用storbinary时触发报错:

ValueError: I/O operation on closed file.

我尝试用open(bytes, "rb")重新打开该对象,结果报错:

expected str, bytes or os.PathLike object, not _io.BytesIO

完整报错堆栈:

Traceback (most recent call last):
  File "/home/airflow/.local/lib/python3.7/site-packages/airflow/operators/python.py", line 171, in execute
    return_value = self.execute_callable()
  File "/home/airflow/.local/lib/python3.7/site-packages/airflow/operators/python.py", line 189, in execute_callable
    return self.python_callable(*self.op_args, **self.op_kwargs)
  File "/opt/airflow/dags/accounting/main.py", line 29, in process_events
    result = Uploader(logger, cfg).send_data(csv_file)
  File "/opt/airflow/dags/accounting/packages/uploader.py", line 57, in send_data
    self.__ftp.storbinary(f"STOR {filename}", bytes)
  File "/usr/local/lib/python3.7/ftplib.py", line 513, in storbinary
    buf = fp.read(blocksize)
ValueError: I/O operation on closed file.

解决方案

1. 先明确核心问题

TextIOWrapper默认会在关闭时连带关闭底层的BytesIO对象,加上Airflow上下文可能自动处理文件对象,导致传入send_data的BytesIO已处于关闭状态。另外,open()仅用于磁盘文件操作,无法直接打开BytesIO对象。

2. 具体修复方案

方案一:避免BytesIO被提前关闭

修改to_csv函数,创建TextIOWrapper时指定closefd=False,防止关闭文本包装器时连带关闭底层的BytesIO:

def to_csv(events: list) -> io.BytesIO():
    if not events:
        return None
    bio = io.BytesIO()
    # 添加closefd=False,隔离TextIOWrapper和BytesIO的关闭操作
    iow = io.TextIOWrapper(bio, closefd=False)
    writer = csv.DictWriter(iow, fieldnames=events[0].keys())
    writer.writeheader()
    writer.writerows(events)
    iow.flush()
    bio.seek(0)
    return bio

方案二:基于已关闭对象的数据重新创建BytesIO

BytesIO即使关闭,仍可通过getvalue()获取存储的字节数据,基于此重新创建可用的BytesIO对象:

def send_data(self, bytes_obj: io.BytesIO) -> str:
    filename = f"{time.time()}.csv"
    if not bytes_obj:
        self.__logger.warning("No data to send")
        return None
    try:
        # 尝试重置指针,若触发关闭错误则重建对象
        try:
            bytes_obj.seek(0)
        except ValueError:
            data = bytes_obj.getvalue()
            bytes_obj = io.BytesIO(data)
        self.__ftp.storbinary(f"STOR {filename}", bytes_obj)
    except ftp.all_errors as e:
        self.__logger.error(
            "FTP fail data send",
            extra={
                "host": self.__cfg.ftp.host,
                "type": type(e).__name__,
                "line": e.__traceback__.tb_lineno,
                "file": __file__,
                "detail": e,
            },
        )
        return None

方案三:提前备份字节数据

在调用send_data前,先提取BytesIO中的数据,后续用新的BytesIO对象传入:

# 生成CSV后备份数据
csv_file = to_csv(events)
if csv_file:
    csv_data = csv_file.getvalue()
    # 用备份数据创建新的BytesIO传入send_data
    result = Uploader(logger, cfg).send_data(io.BytesIO(csv_data))

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 10:15:34