你能告诉我如何读取Python包中的文件吗?
我的情况
我加载的包有许多模板(作为字符串使用的文本文件),我想从程序中加载它们。但是我如何指定这样的文件的路径?
假设我想读取一个文件:
package\templates\temp_file
某种路径操作?包基本路径跟踪?
你能告诉我如何读取Python包中的文件吗?
我的情况
我加载的包有许多模板(作为字符串使用的文本文件),我想从程序中加载它们。但是我如何指定这样的文件的路径?
假设我想读取一个文件:
package\templates\temp_file
某种路径操作?包基本路径跟踪?
当前回答
10.8.中的内容。在Python Cookbook第三版中,David Beazley和Brian K. Jones给出了答案。
我把它放到这里:
假设你有一个包,里面的文件组织如下:
mypackage/
__init__.py
somedata.dat
spam.py
现在假设spam.py文件想要读取文件somedata.dat的内容。要做 它,使用以下代码:
import pkgutil
data = pkgutil.get_data(__package__, 'somedata.dat')
结果变量数据将是一个字节字符串,包含文件的原始内容。
get_data()的第一个参数是一个包含包名的字符串。你可以 要么直接提供它,要么使用一个特殊的变量,比如__package__。第二个 参数是包中文件的相对名称。如果有必要,您可以导航 使用标准的Unix文件名约定进入不同的目录,只要 Final目录仍然位于包中。
这样,包可以安装为目录,.zip或.egg。
其他回答
如果你有这样的结构
lidtk
├── bin
│ └── lidtk
├── lidtk
│ ├── analysis
│ │ ├── char_distribution.py
│ │ └── create_cm.py
│ ├── classifiers
│ │ ├── char_dist_metric_train_test.py
│ │ ├── char_features.py
│ │ ├── cld2
│ │ │ ├── cld2_preds.txt
│ │ │ └── cld2wili.py
│ │ ├── get_cld2.py
│ │ ├── text_cat
│ │ │ ├── __init__.py
│ │ │ ├── README.md <---------- say you want to get this
│ │ │ └── textcat_ngram.py
│ │ └── tfidf_features.py
│ ├── data
│ │ ├── __init__.py
│ │ ├── create_ml_dataset.py
│ │ ├── download_documents.py
│ │ ├── language_utils.py
│ │ ├── pickle_to_txt.py
│ │ └── wili.py
│ ├── __init__.py
│ ├── get_predictions.py
│ ├── languages.csv
│ └── utils.py
├── README.md
├── setup.cfg
└── setup.py
你需要这样的代码:
import pkg_resources
# __name__ in case you're within the package
# - otherwise it would be 'lidtk' in this example as it is the package name
path = 'classifiers/text_cat/README.md' # always use slash
filepath = pkg_resources.resource_filename(__name__, path)
奇怪的“总是使用斜杠”部分来自setuptools api
还要注意,如果使用路径,必须使用正斜杠(/)作为路径分隔符,即使在Windows上也是如此。Setuptools在构建时自动将斜杠转换为适当的平台特定的分隔符
如果你想知道文档在哪里:
PEP 0365 https://packaging.python.org/guides/single-sourcing-package-version/
TLDR;使用标准库的importlib。资源模块,详见下面的方法2。
不再推荐使用setuptools中的传统pkg_resources,因为新的方法:
它的性能显著提高; Is更安全,因为使用包(而不是路径-sting)会引发编译时错误; 它更直观,因为你不需要“连接”路径; 它在开发时更快,因为你不需要额外的依赖项(setuptools),而是只依赖Python的标准库。
我先列出了传统方法,以解释移植现有代码时与新方法的区别(这里也解释了移植)。
让我们假设你的模板位于模块包内嵌套的文件夹中:
<your-package>
+--<module-asking-the-file>
+--templates/
+--temp_file <-- We want this file.
注1:当然,我们不应该篡改__file__属性(例如,当使用zip文件时,代码将中断)。 注2:如果您正在构建这个包,请记住在setup.py中将数据文件声明为package_data或data_files。
1)从setuptools中使用pkg_resources(慢)
你可以使用setuptools发行版中的pkg_resources包,但这是有成本的,性能方面:
import pkg_resources
# Could be any dot-separated package/module name or a "Requirement"
resource_package = __name__
resource_path = '/'.join(('templates', 'temp_file')) # Do not use os.path.join()
template = pkg_resources.resource_string(resource_package, resource_path)
# or for a file-like stream:
template = pkg_resources.resource_stream(resource_package, resource_path)
小贴士: 这将读取数据,即使您的发行版是压缩的,所以您可以在setup.py中设置zip_safe=True,和/或使用期待已久的来自python-3.5的zipapp封装器来创建自包含的发行版。 记住在运行时需求中添加setuptools(例如在install_requires '中)。
... 注意,根据Setuptools/pkg_resources文档,你不应该使用os.path.join:
基本资源接入 注意,资源名必须以/-路径分隔,不能是绝对路径(即没有前导/),也不能包含相对名称,如“..”。不要使用os。路径例程来操作资源路径,因为它们不是文件系统路径。
2) Python >= 3.7,或者使用反向移植的importlib_resources库
使用标准库的importlib。资源模块比setuptools更有效,上面:
try:
import importlib.resources as pkg_resources
except ImportError:
# Try backported to PY<37 `importlib_resources`.
import importlib_resources as pkg_resources
from . import templates # relative-import the *package* containing the templates
template = pkg_resources.read_text(templates, 'temp_file')
# or for a file-like stream:
template = pkg_resources.open_text(templates, 'temp_file')
注意: 关于函数read_text(package, resource): 包可以是字符串,也可以是模块。 资源不再是一个路径,而只是一个文件名的资源打开,在一个现有的包;它可能不包含路径分隔符,也可能没有子资源(即它不能是目录)。
对于问题中问到的例子,我们现在必须:
通过在<your_package>/templates/中创建一个空的__init__.py文件,使<your_package>/templates/成为一个合适的包, 所以现在我们可以使用一个简单的(可能是相对的)import语句(不再解析包/模块名称), 只需请求resource_name = "temp_file"(没有路径)。
Tips: To access a file inside the current module, set the package argument to __package__, e.g. pkg_resources.read_text(__package__, 'temp_file') (thanks to @ben-mares). Things become interesting when an actual filename is asked with path(), since now context-managers are used for temporarily-created files (read this). Add the backported library, conditionally for older Pythons, with install_requires=[" importlib_resources ; python_version<'3.7'"] (check this if you package your project with setuptools<36.2.1). Remember to remove setuptools library from your runtime-requirements, if you migrated from the traditional method. Remember to customize setup.py or MANIFEST to include any static files. You may also set zip_safe=True in your setup.py.
10.8.中的内容。在Python Cookbook第三版中,David Beazley和Brian K. Jones给出了答案。
我把它放到这里:
假设你有一个包,里面的文件组织如下:
mypackage/
__init__.py
somedata.dat
spam.py
现在假设spam.py文件想要读取文件somedata.dat的内容。要做 它,使用以下代码:
import pkgutil
data = pkgutil.get_data(__package__, 'somedata.dat')
结果变量数据将是一个字节字符串,包含文件的原始内容。
get_data()的第一个参数是一个包含包名的字符串。你可以 要么直接提供它,要么使用一个特殊的变量,比如__package__。第二个 参数是包中文件的相对名称。如果有必要,您可以导航 使用标准的Unix文件名约定进入不同的目录,只要 Final目录仍然位于包中。
这样,包可以安装为目录,.zip或.egg。
公认的答案应该是使用importlib.resources。pkgutil。Get_data还要求参数包是非命名空间包(参见pkgutil文档)。因此,包含资源的目录必须有一个__init__.py文件,使其具有与importlib.resources完全相同的限制。如果pkg_resources的开销问题不是问题,这也是一个可以接受的替代方案。
在python -3.3之前,所有包都必须具有__init__.py。在python -3.3之后,文件夹不需要__init__.py作为包。这称为命名空间包。不幸的是,pkgutil不能使用名称空间包(参见pkgutil文档)。
例如,对于包结构:
+-- foo/
| +-- __init__.py
| +-- bar/
| | +-- hi.txt
其中Hi .txt只有Hi!,得到如下结果
>>> import pkgutil
>>> rsrc = pkgutil.get_data("foo.bar", "hi.txt")
>>> print(rsrc)
None
然而,在bar中使用__init__.py,你会得到
>>> import pkgutil
>>> rsrc = pkgutil.get_data("foo.bar", "hi.txt")
>>> print(rsrc)
b'Hi!'
假设您正在使用卵文件;不提取:
我在最近的一个项目中“解决”了这个问题,通过使用postinstall脚本,它将我的模板从egg (zip文件)提取到文件系统中的适当目录中。这是我发现的最快,最可靠的解决方案,因为使用__path__[0]有时会出错(我不记得名字了,但我至少看到一个库,它在列表前面添加了一些东西!)
此外,egg文件通常会被提取到一个称为“egg缓存”的临时位置。您可以使用环境变量更改该位置,可以在启动脚本之前或稍后,例如。
os.environ['PYTHON_EGG_CACHE'] = path
但是,有pkg_resources可以正确地完成这项工作。