从Python egg中访问文件

7

你好,我正在处理Python打包的相关工作。我有三个非代码文件,分别是['synonyms.csv', 'acronyms.csv', 'words.txt'],它们存在于一个名为Wordproject/WordProject/Repository/DataBank/的文件夹结构中。

  • 我在路径Wordproject/WordProject/Repository/下创建了一个RepositoryReader类。
  • 我编写了一段代码,用于获取RepositoryReader的当前位置,然后查找名为DataBank子目录并在其中查找这3个文件。

问题是当我将代码打成一个egg后运行时,

我的代码会给出以下错误:

Could not find the file at X:\1. Projects\Python\Wordproject\venv\lib\site-packages\Wordproject-1.0-py3.6.egg\Wordproject\Repository\DataBank\synonyms.csv

如果路径是一个egg,它无法获取或读取该文件。有没有什么办法可以解决这个问题?这些文件必须在一个egg中。


你的目标是将这些文件安装在pip install时可以访问的某个位置,还是将它们嵌入到包目录中,并以与访问子模块相同的方式访问它们? - abarnert
@abarnert,实际上我无法将这段代码推送到PyPI,因为这是一个组织问题。但我更感兴趣的是对代码进行混淆,以便即使有访问权限,也没有人能够解编包。因此,我希望文件被嵌入到包内部。 - iam.Carrot
嗯,你不可能从一个egg文件中得到太多的混淆信息。它基本上只是一个zip文件加上一个清单,告诉你所有有趣的文件在哪里,这可能会让一个新手黑客慢下来大约60秒... - abarnert
@abarnert,您有什么推荐适合这种需求的东西吗? - iam.Carrot
取决于你为什么想要混淆代码,但通常最好的答案是:不要尝试;几乎任何你想出来的方法都会比它的价值更高,而且只会给你一种虚假的安全感,阻止你找到更好的解决方案。有些情况下,混淆Python代码(并与某些对手进行潜在的无休止的竞争)是值得做的,但99%的情况下,当人们要求这样做时,他们甚至不知道攻击者可能是谁,也许根本就没有攻击者,而他们试图保护的方式也不会有所帮助。 - abarnert
4个回答

3

egg 文件只是重命名的 .zip 文件。

您可以使用 zipfile 库来打开 egg 文件并提取或读取所需的文件。

import zipfile

zip = zipfile.ZipFile('/path/to/file.egg', 'r')

# open file from within the egg
f = zip.open('synonyms.csv', 'r')
txt = f.read()

那么你的意思是在运行时解压egg文件,然后浏览它?我应该在哪里解压它? - iam.Carrot
@iam.Carrot 更新了我的答案,展示了如何直接从zip归档中读取文件,无需将数据提取到磁盘。 - Brendan Abel
我正在使用 pandas 读取 csv 文件,是否有一种在 pandas 中读取文件的方法? - iam.Carrot
@iam.Carrot 从zip.open返回的对象是一个类似文件的对象,您应该能够直接将其提供给pandas.read_csv - Brendan Abel
我还建议使用上下文管理器!with zipfile.ZipFile('/path/to/file.egg/', 'r') as zip: - Josie Thompson

1

这里有两件不同的事情你可能正在尝试完成:

  • 将数据文件视为包的一部分,就像Python模块一样,并在运行时访问它们,即使它并不是普通目录树也可以。
  • 在pip安装时将数据文件安装到其他位置,以便您可以正常访问。

这两种方法都在PyPA/setuptools文档的数据文件章节中有解释。我认为你想要第一种方法,在运行时访问数据文件的子部分中有介绍:

通常,现有程序会操作包的__file__属性以查找数据文件的位置。然而,这种操作与基于PEP 302的导入钩子(包括从zip文件和Python Eggs中导入)不兼容。强烈建议,如果您正在使用数据文件,则应使用pkg_resourcesResourceManager API来访问它们。 pkg_resources模块作为setuptools的一部分分发,因此,如果您正在使用setuptools来分发您的软件包,没有理由不使用其资源管理API。另请参见访问软件包资源,了解将使用__file__的代码转换为使用pkg_resources的快速示例。
Follow that link, and you will find what appears to be outdated PEAK documentation. However, this is because they are indeed outdated. There is a version buried inside the setuptools docs that may be easier to read and navigate once you locate it.
As stated, you could attempt to use get_data (which will function within an egg/zip) and then fallback to accessing a file (which will function when running from source), but it is recommended to use the wrappers in pkg_resources. Essentially, if your code was previously doing this:
path = os.path.join(__file__, 'Wordproject/WordProject/Repository/DataBank/', datathingy)
with open(path) as f:
    for line in f:
        do_stuff(line)

"...你将把它更改为这个:

"
path = 'Wordproject/WordProject/Repository/DataBank/' + datathingy
f = pkg_resources.resource_stream(__name__, path)
for line in f:
    do_stuff(line.decode())

注意,resource_stream 文件始终以二进制模式打开。因此,如果您想将它们作为文本读取,则需要在其周围包装一个 TextIOWrapper,或对每行进行解码。

说实话,我的第一直觉是使用资源管理器 API 本身。但是我无法使其正常工作。当我传入带有文件夹结构的文件名时,它抛出了一个错误,因此我选择在这里提问。如果您能展示一段示例代码,其中文件 egg 是 WordProject,但它有一个子目录 Repository,并且在该目录中我又有另一个目录 DataBank,我需要从那里读取文件,这将非常有帮助。 - iam.Carrot
我有一个最后的问题,我使用pandascsv文件读取为Dataframe,是否有一种方法可以使用资源管理器API实现这一点? - iam.Carrot
@iam.Carrot - 你有没有得到一个合适的解决方案? - Manish
@Manish 我曾经使用了上述解决方案,利用 pkg_resources 获取文件的基本路径,然后定义了一个相对路径来加载我要加载的文件,并且这个方法是可行的。如果你愿意,我可以分享一个示例。 - iam.Carrot
Python 3.7 添加了 importlib_resources,而 pkg_resources 文档似乎建议人们改用它。 - chrisinmtown
显示剩余3条评论

0

根据文档,我们可以以多种方式读取文件内容。

解决方案1:直接将文件内容读入内存。

无需在本地提取文件。

import zipfile, tempfile
tfile = tempfile.NamedTemporaryFile()
with zipfile.ZipFile('/path/to/egg.egg') as myzip:
    with myzip.open('relative/path/to/file.txt') as myfile:
        tfile.write(myfile.read())

# .. do something with temporary file

tfile.close()

现在tfile是您的本地临时文件句柄。它的名称为tfile.name,所有文件操作(如open(tfile)等)都像往常一样工作。tfile.close()必须在最后调用以关闭句柄。

文件内容可以通过myfile.read()本身读取,但是我们一旦退出上下文,就会失去myfile句柄。因此,如果需要将文件内容传递给其他操作,则将其复制到临时文件中。

解决方案2:从egg中提取成员并保存到本地

zipfile提供了一个API来提取特定的成员。

import zipfile
x = zipfile.ZipFile('/path/to/egg.egg')
x.extractall(path='temp/dest/folder', members=['path/to/file.txt'])

解决方案三:提取整个egg文件

另一个解决方案是将egg文件提取到临时文件夹中,然后读取该文件。可以通过以下命令在命令行中提取egg文件。

python -m zipfile -e path/to/my.egg ./temp_destination

0
如果您正在使用Python 3.7或更高版本,我建议使用importlib_resources。从他们的文档https://importlib-resources.readthedocs.io/en/latest/using.html中,这是一个将YAML文件嵌入到模块中的示例:
from importlib_resources import files, as_file

yaml_path = files('my-module').joinpath('openapi.yml')
with as_file(yaml_path) as yaml:
    conn_app.add_api(yaml)

如果模块是通过pip3 install .安装在目录中的,那么这将起作用。 并且如果通过python3 setup.py install安装为egg(zip)文件,也会起作用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接