如何在Python中使用pkgutils.get_data与csv.reader?

6 投票
4 回答
6863 浏览
提问于 2025-04-16 11:52

我有一个Python模块,里面有很多数据文件(是一组表示曲线的csv文件),这些文件需要在运行时加载。csv模块工作得很好。

  # curvefile = "ntc.10k.csv"
  raw = csv.reader(open(curvefile, 'rb'), delimiter=',')

但是如果我把这个模块导入到另一个脚本里,我就需要找到数据文件的完整路径。

/project
   /shared
       curve.py
       ntc.10k.csv
       ntc.2k5.csv
   /apps
       script.py

我希望script.py只用基本的文件名来引用这些曲线,而不是用完整路径。在模块代码中,我可以使用:

pkgutil.get_data("curve", "ntc.10k.csv") 

这样可以很好地找到文件,但它返回的是已经读取的csv文件,而csv.reader需要的是文件句柄本身。有没有办法让这两个模块能够很好地配合?它们都是标准库模块,所以我本来不期待会有问题。我知道我可以开始拆分pkgutil的二进制文件数据,但那样的话我还不如不使用csv库。

我知道我可以在模块代码中直接使用这个,完全不考虑pkgutils,但感觉pkgutils正是为这个目的而存在的。

this_dir, this_filename = os.path.split(__file__)
DATA_PATH = os.path.join(this_dir, curvefile)
raw = csv.reader(open(DATA_PATH, "rb"))

4 个回答

1

这个问题在问出来的十多年后,我通过谷歌找到了这里,并且看了其他回答的内容。现在看来,这个事情变得简单多了。下面是我用标准库中的 importlib 实现的代码,它可以返回包资源的文件系统路径,结果是一个字符串。这个方法应该适用于 Python 3.6 及以上版本。

import importlib.resources
import os


def get_data_file_path(package: str, resource: str) -> str:
    """
    Returns the filesystem path of a resource marked as package
    data of a Python package installed.

    :param package: string of the Python package the resource is
                    located in, e.g. "mypackage.module"
    :param resource: string of the filename of the resource (do not
                     include directory names), e.g. "myfile.png"
    :return: string of the full (absolute) filesystem path to the
             resource if it exists.
    :raises ModuleNotFoundError: In case the package `package` is not found.
    :raises FileNotFoundError: In case the file in `resource` is not
                               found in the package.
    """
    # Guard against non-existing files, or else importlib.resources.path
    # may raise a confusing TypeError.
    if not importlib.resources.is_resource(package, resource):
        raise FileNotFoundError(f"Python package '{package}' resource '{resource}' not found.")

    with importlib.resources.path(package, resource) as resource_path:
        return os.fspath(resource_path)
9

我查看了get_data的源代码,发现其实很简单,可以让它返回文件的路径,而不是直接返回文件内容。这个模块可以解决这个问题。你可以使用关键字as_string=True来返回读取到内存中的文件内容,或者使用as_string=False来返回文件的路径。

import os, sys

from pkgutil import get_loader

def get_data_smart(package, resource, as_string=True):
"""Rewrite of pkgutil.get_data() that actually lets the user determine if data should
be returned read into memory (aka as_string=True) or just return the file path.
"""

loader = get_loader(package)
if loader is None or not hasattr(loader, 'get_data'):
    return None
mod = sys.modules.get(package) or loader.load_module(package)
if mod is None or not hasattr(mod, '__file__'):
    return None

# Modify the resource name to be compatible with the loader.get_data
# signature - an os.path format "filename" starting with the dirname of
# the package's __file__
parts = resource.split('/')
parts.insert(0, os.path.dirname(mod.__file__))
resource_name = os.path.join(*parts)
if as_string:
    return loader.get_data(resource_name)
else:
    return resource_name
2

这不是最理想的办法,特别是对于非常大的文件来说,但你可以使用 StringIO 把一个字符串变成可以用 read() 方法读取的东西,这样 csv.reader 就能处理它了。

csvdata = pkgutil.get_data("curve", "ntc.10k.csv") 
csvio = StringIO(csvdata)
raw = csv.reader(csvio)

撰写回答