NumPy 新改进:`DataSource` 与 `Repository` 全面支持 `os.PathLike` 路径对象
2026/9/20 0:06:05 网站建设 项目流程
  • 科学计算
  • 数据分析

【免费下载链接】numpy

The fundamental package for scientific computing with Python.

项目地址:https://gitcode.com/gh_mirrors/nu/numpy
点击查看免费下载

导读

本篇文章聚焦 NumPy 近期一项面向日常数据文件操作的改进:numpy.lib._datasource.DataSourceRepository类,以及模块级numpy.lib._datasource.open辅助函数,此前只接受字符串形式的本地路径,如今在openexistsabspath等核心方法中同样接受os.PathLike对象(如pathlib.Path实例)。读完本文,你将了解该改动的背景、受影响的 API 清单、底层实现机制,以及如何在科学计算脚本中直接用Path对象读写本地与远程数据文件。

该改进源自 doc/release/upcoming_changes/31906.improvement.rst,本文在忠实还原其内容的基础上,结合_datasource模块源码与其测试用例进行深度展开。

一、改动背景:字符串路径的历史包袱

_datasource模块是 NumPy 中负责"本地 + 远程数据文件"访问的统一文件接口,其模块文档描述如下:

The goal of datasource is to abstract some of the file system operations when dealing with data files so the researcher doesn't have to know all the low-level details. Through datasource, a researcher can obtain and use a file with one function call, regardless of location of the file.

即:无论数据文件位于本地磁盘、http/ftp 远程服务器,还是 gzip/bz2/xz 压缩包中,调用者都可以用一次函数调用拿到可用的文件对象,无需关心底层细节。

在改动之前,该模块的核心入口方法只接受普通字符串路径;而现代 Python 生态中,pathlib.Path已成为文件路径的标准表示方式(Python 3.6 起os.PathLike协议正式确立)。NumPy 的numpy.loadtxtnumpy.save等高层函数早已支持Path对象(参见 numpy/lib/tests/test_io.py 中的TestPathUsage测试),而_datasource作为npyio底层的文件打开通道却一度成为例外。本次改进正是补齐了这一缺口。

二、受影响的 API 清单

根据变更说明原文,以下 API 现在接受os.PathLike本地路径:

API位置说明
DataSource.open(path, mode, encoding, newline)numpy/lib/_datasource.py打开本地文件或下载并打开远程 URL
DataSource.exists(path)numpy/lib/_datasource.py依次检查本地文件、本地缓存、远程 URL 是否存在
DataSource.abspath(path)numpy/lib/_datasource.py返回文件在 DataSource 目录中的绝对路径
Repository.open / exists / abspathnumpy/lib/_datasource.py上述方法在Repository中的对应实现(先拼 baseurl 再委托给DataSource
模块级open(path, mode, destpath, encoding, newline)numpy/lib/_datasource.py便捷函数,内部实例化DataSource并调用其open

注意:DataSourceRepository的构造函数参数destpathRepositorybaseurl参数同样在文档签名中标注为 "str or path-like"(见 DataSource.init与 Repository.init),因此你可以直接用Path对象初始化它们。

三、底层实现机制:os.fspath()统一收敛

源码层面的实现非常简洁且统一:所有公开入口在处理路径时首先调用os.fspath(path),把PathLike对象规范化为字符串,再走原有的字符串路径逻辑。

以三个核心方法为例:

# DataSource.open 的入口 path = os.fspath(path) # DataSource.exists 的入口 path = os.fspath(path) # DataSource.abspath 的入口 path = os.fspath(path)

os.fspath()是 Python 3.6 引入的标准函数:对于strbytes直接原样返回;对于实现了__fspath__()协议的对象(典型如pathlib.Path)返回其字符串表示;否则抛出TypeError。因此这次改动实际上是把"字符串专享"的 API 收敛成了"一切遵循os.PathLike协议的对象"。

模块级便捷函数open同样遵守该协议(numpy/lib/_datasource.py):

path = os.fspath(path) ds = DataSource(destpath) return ds.open(path, mode, encoding=encoding, newline=newline)

Repository则通过_fullpath在拼接baseurl时先做os.fspath(numpy/lib/_datasource.py),随后把完整路径委托给父类DataSource的对应方法,因此Repository的所有入口同样自动获得了PathLike支持。

从源码结构可以推断:os.fspath被统一放在方法入口处,意味着所有内部路径处理逻辑(压缩扩展名探测、URL 解析、缓存定位等)无需改动,即可天然兼容Path对象,这也是该改动保持低侵入、低风险的关键。

四、测试佐证:测试用例如何验证PathLike支持

该改动并非孤立的文档声明,numpy/lib/tests/test__datasource.py 中为每个 API 都补充了对应的PathFile测试,形成完整闭环:

class TestDataSourceOpen: def test_PathFile(self, tmp_path): ds = datasource.DataSource(tmp_path) local_file = Path(valid_textfile(tmp_path)) with ds.open(local_file) as fh: assert_(fh) class TestDataSourceExists: def test_PathFile(self, tmp_path): ds = datasource.DataSource(tmp_path) tmpfile = Path(valid_textfile(tmp_path)) assert_(ds.exists(tmpfile)) missing = tmp_path / "missing.txt" assert_equal(ds.exists(missing), False) class TestDataSourceAbspath: def test_PathFile(self, tmp_path): ds = datasource.DataSource(tmp_path) tmpfile = valid_textfile(tmp_path) assert_equal(tmpfile, ds.abspath(Path(tmpfile)))

Repository一侧的覆盖同样完整:

  • TestRepositoryAbspath.test_PathFile:验证repos.abspath(Path(tmpfilename))
  • TestRepositoryExists.test_PathFile:验证repos.exists(Path(tmpfilename))为 True、不存在的Path("missing.txt")为 False;
  • TestRepositoryOpen.test_PathFile:验证repos.open(Path(tmpfilename))能正常返回文件对象;
  • TestOpenFunc.test_PathFile:验证模块级datasource.open(local_file, destpath=tmp_path)接受Path对象。

这些测试同时验证了Path对象在"只传文件名(相对路径)"场景下的行为——测试中Path包装的是文件全路径,但也覆盖了tmpfilename这种仅文件名的情况,确保os.fspath后原有查找逻辑(含destpath拼接)不受影响。

五、实战示例:用pathlib.Path操作数据文件

5.1 本地文件读写

import pathlib from numpy.lib import npyio # 或直接使用 np.lib.npyio.DataSource / np.lib._datasource ds = npyio.DataSource(pathlib.Path("/home/user/data")) local_path = pathlib.Path("/home/user/data/sample.txt") # open:返回普通文件对象,可配合 with 使用 with ds.open(local_path) as fh: content = fh.read() # exists:本地文件是否存在 print(ds.exists(local_path)) # True print(ds.exists(pathlib.Path("/no/such/file.txt"))) # False # abspath:返回 DataSource 目录内的绝对路径 print(ds.abspath(local_path))

5.2 与压缩文件协同

DataSource打开文件时支持透明解压:_FileOpeners会按扩展名自动选择gzip.openbz2.openlzma.open(支持.gz.bz2.xz.lzma),无扩展名则使用内置open(参见 numpy/lib/_datasource.py)。现在这些路径也可以直接传Path对象:

import pathlib import gzip from numpy.lib import npyio # 构造一个 gzip 压缩文件 p = pathlib.Path("/tmp/foobar.txt.gz") with gzip.open(p, 'w') as fp: fp.write(b'three is the magic number') ds = npyio.DataSource(pathlib.Path("/tmp")) with ds.open(p) as fh: # Path 对象,透明解压 print(fh.readline()) # b'three is the magic number'

5.3 远程 URL 与Repository

PathLike支持同样适用于远程场景。DataSource遇到带 scheme 的 URL(如http://ftp://)时会自动下载并缓存到destpath下,然后返回本地缓存的文件对象:

ds = npyio.DataSource("/home/user/cache") fp = ds.open("http://www.example.com/data.txt") # 自动下载并缓存 fp.read() fp.close() print(ds.abspath("http://www.example.com/data.txt")) # '/home/user/cache/www.example.com/data.txt'

Repository适用于"多个文件共享一个 baseurl/基础目录"的场景,初始化参数同样接受Path

from numpy.lib import npyio repos = npyio.Repository(pathlib.Path("/home/user/data/dir/")) for filename in ["a.csv", "b.csv", "c.csv"]: with repos.open(filename) as fp: # 自动拼接 baseurl ...

提示:_datasource模块同时被 NumPy 的文件 IO 高层函数复用,例如 numpy/lib/_npyio_impl.py 中的np.lib._datasource.open(fname, 'rt', encoding=encoding)。因此本次PathLike支持也间接惠及loadtxtsavetxtgenfromtxt等函数的文件打开路径。

六、注意事项与边界

  1. URL 必须带 schemeDataSource判断远程地址依赖urlparse解析出的 scheme 与 netloc(见 _isurl)。ds.exists('www.google.com/index.html')会返回False,而ds.exists('http://www.google.com/index.html')才返回True
  2. URL 不可写open检测到写模式(mode 含'w''+')且路径是 URL 时会抛出ValueError("URLs are not writeable")(numpy/lib/_datasource.py)。
  3. 文件不存在会抛FileNotFoundErroropen_findfile找不到任何候选(含压缩变体)时抛出FileNotFoundError(f"{path} not found.")
  4. 临时目录生命周期:当destpath=None时,DataSource会通过tempfile.mkdtemp()创建临时目录,并在对象被回收(__del__)时递归删除(numpy/lib/_datasource.py)。
  5. 路径沙箱abspath会对传入路径做归一化与净化处理(_sanitize_relative_path),确保最终路径不会逃逸出destpath;测试用例test_sandboxing专门覆盖了/etc/shadow../../shadow等恶意输入(numpy/lib/tests/test__datasource.py)。

七、总结

DataSource/RepositoryPathLike支持是一次小而关键的 API 一致性改进:通过在每个入口统一调用os.fspath(),让 NumPy 底层的数据文件抽象与现代 Python 的pathlib生态无缝衔接。从变更说明(31906.improvement.rst)到实现(numpy/lib/_datasource.py)再到测试(numpy/lib/tests/test__datasource.py),形成了完整的"文档—实现—验证"闭环。

对于日常使用 NumPy 的开发者,这意味着:无论数据文件在本地、在远程服务器、还是压缩打包,你都可以放心地把pathlib.Path对象直接传给DataSourceRepository及其open/exists/abspath方法,再也不用为"先转成字符串"而编写多余的类型转换代码。

  • 科学计算
  • 数据分析

【免费下载链接】numpy

The fundamental package for scientific computing with Python.

项目地址:https://gitcode.com/gh_mirrors/nu/numpy
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询