【问题标题】:Why use importlib.resources over __file__?为什么在 __file__ 上使用 importlib.resources?
【发布时间】:2022-10-05 04:28:49
【问题描述】:

我有一个包裹,就像

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

模块module.py 需要data.csv 来执行某个任务。

我用来访问data.csv 的第一个天真的方法是

# module.py - Approach 1
from pathlib import Path

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

但是当我们通过from mypkg.scripts import module 或类似方法导入module.py 时,这显然会中断。无论mypkg 是从哪里导入的,我都需要一种访问data.csv 的方法。

下一个简单的方法是使用__file__ 属性来访问module.py 模块所在的路径。

# module.py - Approach 2
from pathlib import Path

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

但是,在研究这个问题时,我发现不鼓励这种方法。例如,参见How to read a (static) file from inside a Python package?

尽管对于这个问题的最佳解决方案似乎没有完全一致,但看起来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__.py,我需要导入importlib,并且我需要使用上下文管理器来访问相对路径。

我对importlib 策略的好处缺少什么?为什么不直接使用__file__

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

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

【问题讨论】:

  • 你读过wim's answer吗?这是按“趋势(最近的投票数更多)”排序的最佳答案。它讨论了为什么不使用您提到的任何一个。对于 Python 3.9+,它建议使用 pkgutilimportlib_resources

标签: python resources


【解决方案1】:

你应该能够使用这样的东西:

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


def main():
    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,您可以看到整个运行here

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-26
    • 2011-07-06
    • 1970-01-01
    • 2012-03-26
    • 1970-01-01
    • 2010-12-03
    • 1970-01-01
    • 2014-09-28
    相关资源
    最近更新 更多