如何在Python中使用pkgutils.get_data与csv.reader?
我有一个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)