登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  python教程

Python os.splice 怎样在文件描述符之间传输数据

来源:17golang原创

时间:2026-11-12 01:59:04 156浏览 收藏

如果目标是在 Linux 上把一个文件描述符的数据交给另一个文件描述符,os.splice 的关键不是把数据读进 Python 的 bytes,而是让内核在文件描述符之间完成转移。实际使用时至少要准备一端为管道:先把源文件 splice 到管道写端,再把管道读端 splice 到目标文件。

官方地址:https://docs.python.org/3/library/os.html#os.splice

调用时要记住三件事:返回值是本次实际传输的字节数,短传输必须继续循环;返回 0 表示输入端结束;管道对应的偏移量必须传 None。os.splice 从 Python 3.10 提供,官方文档标注的系统可用范围是 Linux。

先看清 os.splice 的传输模型

os.splice(src, dst, count, offset_src=None, offset_dst=None, flags=0) 的参数顺序是源描述符、目标描述符、本次最多传输的字节数、源偏移、目标偏移和 flags。它不是“两个普通文件直接互拷”的通用包装:至少有一个描述符必须指向管道,普通文件到普通文件的调用不符合这个接口的约束。

Python os.splice 通过内核管道在源文件描述符和目标文件描述符之间传输数据的结构说明图
图1:os.splice 通过内核管道连接源文件和目标文件的结构图;用于理解关系,不代表运行截图。

因此,常见的文件复制拓扑是“源文件 → 管道写端 → 管道读端 → 目标文件”。中间的管道不是为了让 Python 代码手工搬运数据,而是满足 splice 的内核传输路径,同时把一次大传输拆成可控制的块。

用管道连接源文件和目标文件

下面的示例把源文件按块送入管道,再从管道读端送入目标文件。代码只展示调用结构和错误处理约定,不把任何静态图或示例描述当作本机运行结果。

import os


CHUNK_SIZE = 1024 * 1024


def splice_copy(source: str, target: str) -> int:
    source_fd = target_fd = pipe_read = pipe_write = None
    total = 0
    try:
        # 源文件只读,目标文件不存在时创建并在开始时截断。
        source_fd = os.open(source, os.O_RDONLY)
        target_fd = os.open(
            target,
            os.O_WRONLY | os.O_CREAT | os.O_TRUNC,
            0o644,
        )
        # 管道让两次 splice 都满足“至少一端是 pipe”的约束。
        pipe_read, pipe_write = os.pipe()

        while True:
            # 从源文件进入管道;None 表示使用并更新源文件当前位置。
            moved_to_pipe = os.splice(source_fd, pipe_write, CHUNK_SIZE)
            if moved_to_pipe == 0:
                break

            remaining = moved_to_pipe
            while remaining:
                # 从管道进入目标文件;管道一端不能传显式 offset。
                moved_to_target = os.splice(pipe_read, target_fd, remaining)
                if moved_to_target == 0:
                    raise OSError("管道没有完成预期的输出")
                remaining -= moved_to_target
                total += moved_to_target
        return total
    finally:
        # 无论成功、EOF 还是异常,都关闭本次创建的全部描述符。
        for fd in (pipe_read, pipe_write, source_fd, target_fd):
            if fd is not None:
                os.close(fd)


if __name__ == "__main__":
    # 真实项目可在这里接入参数解析;示例只保留函数调用入口。
    try:
        copied = splice_copy("source.bin", "target.bin")
        print(f"传输完成:{copied} 字节")
    except AttributeError as exc:
        # 当前 Python 或平台没有暴露 os.splice 时给出明确原因。
        raise RuntimeError("当前环境不提供 os.splice") from exc
    except OSError as exc:
        # 权限、文件系统和内核约束都通过 OSError 暴露。
        raise RuntimeError(f"splice 传输失败:{exc}") from exc

这里用了两层循环。外层负责不断从源文件取块,内层负责把本次进入管道的字节全部排到目标文件。不能假设一次 os.splice 一定返回请求的 count,否则在管道容量、非阻塞模式或底层 I/O 状态变化时可能留下未写完的数据。

处理偏移量、短传输和 EOF

对普通文件来说,offset_src=None 或 offset_dst=None 表示使用文件当前位置;传入整数则表示从指定偏移读或写。管道没有可供随机定位的文件位置,所以对应的 offset 必须是 None。如果需要重复读取同一个源文件片段,应该重新定位普通文件描述符,不能给管道设置偏移。

Python os.splice 的源偏移、目标偏移、管道 offset 限制和 EOF 返回值说明图
图2:os.splice 偏移量、管道约束和返回值语义说明图;用于理解关系,不代表运行截图。
现象应如何判断处理方式
返回值小于 count本次只完成了部分传输按返回值减少剩余字节,继续调用
返回值等于 0输入端没有更多数据,或管道没有可传数据文件复制通常结束;管道场景要结合写端是否仍存在判断
管道一端传了整数 offset参数违反管道定位约束改为 None,只给普通文件使用显式偏移
同一块数据要排空源到管道和管道到目标的返回值可能不同用剩余字节循环,直到本块全部送出

代码中的 moved_to_pipe 只表示进入管道的字节数;它不等于已经写入目标文件的字节数。只有内层循环把 remaining 减到零,外层才可以开始下一块。这个区分是避免尾部数据丢失的关键。

选择 flags 并控制阻塞行为

flags 可以用按位或组合官方提供的标志,但不要把它们当成“开启零拷贝”的开关,数据路径本身已经由 os.splice 决定。

  • os.SPLICE_F_MOVE:请求内核尽量移动管道页,而不是复制页;内核无法移动时仍可能复制。
  • os.SPLICE_F_NONBLOCK:请求管道操作非阻塞,但相关文件描述符本身仍可能使调用阻塞,不能只看这个 flag 判断整个调用一定立即返回。
  • os.SPLICE_F_MORE:提示后续还会有数据,适合连续分块传输的场景,但它是提示,不是完成语义。

如果使用 SPLICE_F_NONBLOCK,还要为 BlockingIOError 设计重试或事件循环策略;单纯把 flag 加上而不处理暂时不可读、不可写,会把“稍后继续”误判为传输失败。

排查平台和文件系统限制

  1. 先确认运行平台。官方文档把 os.splice 标为 Linux API,其他系统不能按相同代码假设可用。
  2. 再确认 Python 版本。该接口从 Python 3.10 加入,过低版本可能没有这个属性。
  3. 检查管道约束。源和目标都为普通文件时,必须加入管道中转;管道对应的 offset 只能为 None。
  4. 检查文件系统关系。官方文档说明相关文件不在同一文件系统时可能以 EXDEV 失败,跨挂载点复制应准备普通读写或其他兼容路径。
  5. 保留真实的 OSError。权限、无效描述符、内核不支持和文件系统限制不应被统一改写成“复制失败”而丢掉 errno。

常见问题

os.splice 能不能直接把一个普通文件复制到另一个普通文件?

不能按普通文件到普通文件的方式直接假设可用。至少一端必须是管道,文件复制通常采用“源文件—管道—目标文件”的两次调用。

为什么一次 splice 返回的字节数比 count 小?

系统调用返回的是本次实际传输量,不承诺一次填满请求数量。管道容量和底层 I/O 状态都可能让它短传输,因此要按返回值继续循环。

什么时候 offset 必须写 None?

只要对应的描述符是管道,就不能使用显式偏移。普通文件可以用 None 表示当前位置,也可以使用整数偏移。

os.splice 和 Python 的 file.read、file.write 有什么区别?

read 与 write 会把数据带入 Python 用户态;os.splice 面向文件描述符和内核管道,目标是减少内核与用户态之间的额外搬运,但平台和文件系统约束也更严格。

总结:Python os.splice 的使用重点是先设计好“文件描述符—管道—文件描述符”的拓扑,再用循环处理短传输和 EOF。偏移量只给普通文件使用,管道一端保持 None,最后在 finally 中关闭所有描述符,才能把这个 Linux 专用低层接口用得稳定。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>