这是认可的方法来访问与Python脚本相邻/打包的数据吗?

Omn*_*ous 7 python resources packaging

我有一个Python脚本,该脚本需要一些存储在文件中的数据,这些数据将始终与该脚本位于同一位置。我有一个setup.py脚本脚本,我想确保它可以在各种环境中安装为pip,并且在必要时可以将其转换为独立的可执行文件。

目前,该脚本可在Python 2.7和Python 3.3或更高版本上运行(尽管我没有针对3.3的测试环境,所以我不确定)。

我想出了这种方法来获取数据。该脚本不是包含__init__.py任何内容的模块目录的一部分,它只是一个独立文件,如果直接运行就可以使用python,而且在setup.py文件中定义了入口点。全部都是一个文件。这是正确的方法吗?

def fetch_wordlist():
    wordlist = 'wordlist.txt'
    try:
        import importlib.resources as res
        return res.read_binary(__file__, wordlist)
    except ImportError:
        pass
    try:
        import pkg_resources as resources
        req = resources.Requirement.parse('makepw')
        wordlist = resources.resource_filename(req, wordlist)
    except ImportError:
        import os.path
        wordlist = os.path.join(os.path.dirname(__file__), wordlist)
    with open(wordlist, 'rb') as f:
        return f.read()
Run Code Online (Sandbox Code Playgroud)

这似乎很复杂。而且,它似乎以我不满意的方式依赖于软件包管理系统。除非已通过pip安装,否则该脚本将不再起作用,这似乎也不可取。

BPL*_*BPL 7

Resources living on the filesystem

The standard way to read a file adjacent to your python script would be:

a) If you've got python>=3.4 I'd suggest you use the pathlib module, like this:

from pathlib import Path


def fetch_wordlist(filename="wordlist.txt"):
    return (Path(__file__).parent / filename).read_text()


if __name__ == '__main__':
    print(fetch_wordlist())
Run Code Online (Sandbox Code Playgroud)

b) And if you're still using a python version <3.4 or you still want to use the good old os.path module you should do something like this:

import os


def fetch_wordlist(filename="wordlist.txt"):
    with open(os.path.join(os.path.dirname(__file__), filename)) as f:
        return f.read()


if __name__ == '__main__':
    print(fetch_wordlist())
Run Code Online (Sandbox Code Playgroud)

Also, I'd suggest you capture exceptions in the outer callers, the above methods are standard way to read files in python so you don't need wrap them in a function like fetch_wordlist, said otherwise, reading files in python is an "atomic" operation.

Now, it may happen that you've frozen your program using some freezer such as cx_freeze, pyinstaller or similars... in that case you'd need to detect that, here's a simple way to check it out:

a) using os.path:

if getattr(sys, 'frozen', False):
    app_path = os.path.dirname(sys.executable)
elif __file__:
    app_path = os.path.dirname(__file__)
Run Code Online (Sandbox Code Playgroud)

b) using pathlib:

if getattr(sys, 'frozen', False):
    app_path = Path(sys.executable).parent
elif __file__:
    app_path = Path(__file__).parent
Run Code Online (Sandbox Code Playgroud)

Resources living inside a zip file

The above solutions would work if the code lives on the file system but it wouldn't work if the package is living inside a zip file, when that happens you could use either importlib.resources (new in version 3.7) or pkg_resources combo as you've already shown in the question (or you could wrap up in some helpers) or you could use a nice 3rd party library called importlib_resources that should work with the old&modern python versions:

Specifically for your particular problem I'd suggest you take a look to this https://importlib-resources.readthedocs.io/en/latest/using.html#file-system-or-zip-file.

If you want to know what that library is doing behind the curtains because you're not willing to install any 3rd party library you can find the code for py2 here and py3 here in case you wanted to get the relevant bits for your particular problem


Tom*_*now 5

我将大胆地做一个假设,因为它可以大大简化您的问题。我可以想象的唯一方法是,您可以声称此数据“存储在与脚本始终位于同一位置的文件中”,是因为您一次创建了此数据,并将其放在源代码中的文件中目录。即使此数据是二进制数据,您是否考虑过将数据作为python文件中的原义字节串,然后像其他操作一样简单地将其导入?