编程 range-streams:在 Python 里用 HTTP Range 请求流式读取文件并支持随机 seek

2026-10-04 00:03:29

range-streams:在 Python 里用 HTTP Range 请求流式读取文件并支持随机 seek

GitHub 仓库(MIT,Python 3.10+,pip install range-streams)
文档
PyPI

RangeStream 面向「服务器支持 range 请求」这一前提,把一个远端文件包装成类似标准库 io 的文件对象:请求哪一段就下载哪一段,并记录已经请求过的区间。

初始化

RangeStream 初始化时需要提供:

  • 一个 URL,即要流式读取的文件;
  • 一个 client(例如 httpx.Client),不传则新建一个;
  • 可选的 range,形式可以是 python-ranges 包里的 ranges.Range(推荐),也可以是两个整数组成的元组,按区间记法视为半开区间 [start, stop)。

每个 range 请求都会返回内容总长度,因此在第一次 range 请求完成之后,RangeStream 就能支持负值范围,也就是相对文件末尾定位。

初始化时若不传 range,默认是 [0,0),即空范围,此时发送的是 HEAD 请求而不是 GET,用来获取文件的总长度。这一步只会把总长度写到 RangeStream 的 _length 属性上,通过 total_bytes 属性读取。

请求范围

一旦针对非空范围发起请求,RangeStream 会取到存放在 ._ranges 属性上的 RangeDict 中的第一个条目。使用时应访问 ranges 属性而不是内部的 _ranges,因为 ranges 会考虑每个范围的 RangeResponse 中的字节是否已耗尽,或者因与另一个范围重叠而被移除。

from range_streams import RangeStream, _EXAMPLE_URL
stream = RangeStream(url=_EXAMPLE_URL)
stream.add(byte_range=(0,3))  # or pass ranges.Range(0,3)
stream.ranges
# RangeDict{
#   RangeSet{Range[0, 3)}: ... [0, 3)
# }

继续请求其他范围,调用 RangeStream.add,参数可以是另一个 Range 对象,也可以是两个整数组成的元组(按 [a,b) 半开区间解释):

stream.add(byte_range=(7,9))
stream.ranges
# RangeDict{
#   RangeSet{Range[0, 3)}: RangeResponse [0, 3) @ 'example_text_file.txt' from raw.githubusercontent.com,
#   RangeSet{Range[7, 9)}: RangeResponse [7, 9) @ 'example_text_file.txt' from raw.githubusercontent.com
# }

类签名:

class range_streams.stream.RangeStream(url, client=None, byte_range=Range[0, 0), pruning_level=0, single_request=False, force_async=False, chunk_size=None, raise_response=True)

API 说明

  • range_streams 提供文件式对象操作,API 对熟悉标准库 io 模块的用户而言是熟悉的。它用 Range、RangeSet、RangeDict(来自 python-ranges 库)在高效的链表数据结构中表示和查找范围操作。
  • 该类表示一个从支持 range 请求的服务器流式读取的文件,ranges 属性给出到目前为止已请求、且尚未耗尽的区间列表。
  • 类初始化时其长度会在第一次 range 请求时被检查;传入的 client 不会被关闭,需要自行处理。后续范围通过 add() 请求。
  • byte_range 可以是 Range 对象,也可以是两个整数组成的二元组 (start, end),按半闭区间 [start, end) 解释。
  • 若 byte_range 是空范围 Range(0,0)(默认值),初始化时会向 url 发送 HEAD 请求,从 content-length 响应头设置 total_bytes。
  • 若 single_request 为 True(默认 False),那么传入空 byte_range 时的行为变为发送标准的流式 GET 请求(完全不是 partial content 请求),类随后提供的接口会「模拟」这些调用,即仿佛每次使用 add() 时 range 请求都立即返回(因为需要的数据在初始化时的第一次请求中已经全部拿到)。线性读取流时性能更好。
  • add(byte_range=Range[0, 0), activate=True, name=''):向流中添加一个范围。如果范围为空且流的长度尚未确定,会发起 HEAD 请求检查文件总大小。其他情况下只把 Range 加入 ranges 的 RangeDict,建立流式 partial content GET 请求,但不尝试从中读取任何字节(因此响应数据会在创建时就被下载)。
  • 存在异步 fetcher 模块、重叠处理模块和流式编解码(streaming codecs)模块。
  • pruning_level 参数控制已耗尽或重叠范围的剪枝。
  • force_async 强制使用异步抓取。
  • chunk_size 控制读取块大小。
  • raise_response 在响应异常时抛出。

设计文档见用户指南中的《Design notes for RangeStream》和《Motivation for range-streams》。

同名概念

其他地方存在同名但完全不同的东西,注意区分:Rust 的 combine::stream::RangeStream trait(零拷贝解析,pub trait RangeStream: Stream + RangeStreamOnce {});fibjs 的 io.RangeStream(在 SeekableStream 上做范围查询的流,new io.RangeStream(stream, '0-10'));Dart 的 bloc_generic_streams 里的 RangeStream(发出范围内整数序列)。它们只是名字相同。

推荐文章

程序员茄子在线接单