为什么使用 importlib.resources 而不是 __file__?

Why use importlib.resources over __file__?

提问人:Jagerber48 提问时间:7/6/2022 最后编辑:Jagerber48 更新时间:10/5/2022 访问量:3128

问:

我有一个包裹,就像

mypkg
    |-mypkg
        |- data
            |- data.csv
            |- __init__.py  # Required for importlib.resources 
        |- scripts
            |- module.py
        |- __init__.py

该模块需要执行特定任务。module.pydata.csv

我曾经访问的第一个幼稚方法是data.csv

# module.py - Approach 1
from pathlib import Path

data_path = Path(Path.cwd().parent, 'data', 'data.csv')

但是,当我们导入VIA或类似产品时,这显然会中断。我需要一种访问方式,无论从哪里导入。module.pyfrom mypkg.scripts import moduledata.csvmypkg

下一个幼稚的方法是使用属性来访问模块所在的路径。__file__module.py

# module.py - Approach 2
from pathlib import Path

data_path = Path(Path(__file__).resolve().parents[1], 'data', 'data.csv')

然而,在研究这个问题时,我发现这种方法是不鼓励的。例如,请参阅如何从 Python 包内部读取(静态)文件?

虽然对于这个问题的最佳解决方案似乎没有完全达成一致,但看起来可能是最受欢迎的。我相信这会是这样的:importlib.resources

# module.py - Approach 3
from pathlib import Path
import importlib.resources

data_path_resource = importlib.resources('mypkg.data', 'data.csv')
with data_path_resources as resource:
    data_path = resource

为什么这个最终方法比?如果源代码被压缩,似乎不起作用。这是我不熟悉的情况,听起来也有点边缘。我不认为我的代码会以压缩方式运行。__file____file__

增加的开销似乎有点荒谬。我需要在数据文件夹中添加一个空,我需要导入,并且我需要使用上下文管理器来访问相对路径。importlib__init__.pyimportlib

关于该策略的好处,我错过了什么?为什么不直接使用?importlib__file__

编辑:该方法的一个可能理由是它略微改进了语义。这应该被认为是包的一部分,所以我们应该使用类似的东西来访问它,但当然这种语法仅适用于导入 python 模块。但是将“从某个包导入某些内容”语义移植到更通用的文件类型。importlibdata.csvfrom mypkg import data.csv.pyimportlib.resources

相比之下,构建相对路径的语法有点像是这样的:这个模块在文件结构中偶然靠近数据文件,所以让我们利用它来访问它。数据文件是包的一部分这一事实不会被利用。__file__

Python 资源

评论

2赞 aaron 9/29/2022
你读过 wim 的回答吗?这是按“趋势(最近的投票计数更多)”排序的顶级答案。它讨论了为什么不使用您提到的任何一个。它建议 ,而对于 Python 3.9+,则推荐。pkgutilimportlib_resources
0赞 Jagerber48 4/23/2023
@aaron我想更好地理解链接问题中的主要答案。(1) 关于拉链/轮子的更多细节是什么?该用例何时会出现,具体情况如何?(2) 在链接答案中的方法中,我想知道如何获取资源的路径,以便我可以使用任何辅助模块(csv、h5 等)打开我拥有的任何类型的二进制文件,而不仅仅是作为二进制文件打开。
0赞 Jagerber48 9/25/2023
我在 python 话语上问了这个问题:discuss.python.org/t/......结论是,对于大多数应用程序来说都很好,而且很容易理解,所以如果它有效,我应该使用它。如果我曾经参与过 zip 应用程序,我想我需要更多地了解其他方法。__file__

答:

3赞 Vinay Sajip 10/5/2022 #1

您应该能够将这样的东西与:__file__

import csv
from io import StringIO
from pathlib import Path
import pkgutil
import sys


def main():
    # Point to appropriate ancestor directory
    p = Path(__file__).parent.parent.parent
    sys.path.insert(0, str(p))
    data = pkgutil.get_data('mypkg.data', 'data.csv')
    reader = csv.reader(StringIO(data.decode()))
    for row in reader:
        print(row)


if __name__ == '__main__':
    main()

如果文件包含data.csv

Col 1,Col 2
v1,v2

然后上面的脚本将打印出来

['Col 1', 'Col 2']
['v1', 'v2']

如果您选择“Shell”选项卡并运行 .python mypkg/scripts/module.py