本地Python包管理:最佳导入方式?

3 投票
2 回答
832 浏览
提问于 2025-04-17 16:46

我需要发布一组使用多个包的Python程序,这些包存放在一个本地的Library目录里。我的目标是让用户在使用我的程序之前不需要安装这些包(这些包已经包含在Library目录中)。那么,导入Library中的包的最佳方法是什么呢?

我尝试了三种方法,但都不太完美:有没有更简单、可靠的方法?或者这些方法中有没有哪一种是最好的?

  1. 第一种方法是直接将Library文件夹添加到库路径中:

    import sys
    import os
    sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'Library'))
    
    import package_from_Library
    

    我把Library文件夹放在最前面,这样我程序里自带的包就会优先于用户安装的同名模块(这样我就能确保他们使用的是正确的版本)。这种方法在Library文件夹不在当前目录时也能工作,这点很好。不过,这种方法也有缺点。我的每个程序都要把同样的路径添加到sys.path中,这样很浪费。此外,所有程序都必须包含相同的三行修改路径的代码,这违反了“不要重复自己”的原则。

  2. 对上述问题的改进是尝试只添加一次Library路径,可以通过在一个导入的模块中实现:

    # In module add_Library_path:
    sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'Library'))
    

    然后在我的每个程序中使用:

    import add_Library_path
    import package_from_Library
    

    这样,得益于CPython的缓存机制,模块add_Library_path只会运行一次,Library路径也只会被添加一次到sys.path中。不过,这种方法的缺点是import add_Library_path会有一个看不见的副作用,而且导入的顺序很重要:这使得代码不太易读,也更脆弱。此外,这还迫使我的程序分发中包含一个用户不会直接使用的add_Library_path.py程序。

  3. 可以通过将Library变成一个包(在里面放一个空的__init__.py文件)来导入Library中的Python模块,这样可以做到:

    from Library import module_from_Library
    

    不过,这种方法在Library中的包时就会出问题,因为它们可能会像这样做from xlutils.filter import …,这会出错,因为xlutilssys.path中找不到。所以,这种方法有效,但只适用于包含在Library中的模块,而不适用于包。

这些方法都有一些缺点。

有没有更好的方法来发布带有一组包(它们使用的)存放在本地Library目录中的程序?或者上述方法(方法1?)是最好的选择吗?

PS:在我的情况下,Library中的所有包都是纯Python包,但如果有一个适用于任何操作系统的更通用的解决方案,那就更好了。

PPS:我的目标是让用户能够使用我的程序,而不需要安装任何东西(除了定期复制我提供的目录),就像上面的例子那样。

PPPS:更确切地说,我的目标是让用户能够轻松更新我的程序集合和它们相关的第三方包,只需简单地复制一个包含我的程序和Library文件夹的目录(里面有“隐藏”的第三方包)。因为我会频繁更新,所以我希望不强迫用户更新他们的Python版本。

2 个回答

1

我觉得你应该看看路径导入钩子,它可以让你修改Python在查找模块时的行为。

举个例子,你可以尝试做一些类似于KDE的脚本引擎为Python插件所做的事情[1]。它会在sys.path中添加一个特殊的标记(比如"<plasmaXXXXXX>",其中XXXXXX是一个随机数字,用来避免名字冲突),然后当Python尝试导入模块但在其他路径中找不到时,它会调用你的导入器来处理这个问题。

还有一个更简单的办法,就是用一个主脚本作为启动器,它只需将路径添加到sys.path中,然后执行目标文件(这样你就可以安全地避免在每个文件中都写sys.path.append(...)这一行)。

还有另一种方法,适用于Python 2.6及以上版本,那就是将库安装在每个用户的site-packages目录下。


[1] 你可以在安装了KDE的Linux系统中找到源代码,路径是/usr/share/kde4/apps/plasma_scriptengine_python

2

随便玩弄 sys.path() 会让你很痛苦…… 现代包模板Distribute 里有很多信息,部分内容就是为了帮你解决问题而设置的。

我建议你设置一个 setup.py 文件,把所有的包安装到一个特定的 site-packages 位置,或者如果可以的话,安装到系统的 site-packages 里。在前一种情况下,本地的 site-packages 会被添加到系统或用户的 PYTHONPATH 中。而在后一种情况下,就不需要做任何更改。

你也可以用批处理文件来设置 Python 的路径。或者把 Python 可执行文件改成指向一个包含修改过的 PYTHONPATH 的 shell 脚本,然后执行 Python 解释器。当然,后者意味着你必须能够访问用户的机器,但你并不能这样做。不过,如果你的用户只是运行脚本而不导入你自己的库,你 可以 为脚本使用你自己的包装器:

#!/path/to/my/python

/path/to/my/python 脚本可能是这样的:

#!/bin/sh
PYTHONPATH=/whatever/lib/path:$PYTHONPATH /usr/bin/python $*

撰写回答