两个安装的模块名称相同,如何选择加载哪一个?

2024-06-01 00:47:37 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在编写一个依赖于特定模块的python program。事实是,PyPI中至少有两个不同的包有一个名为rtmidipython-rtmidi的模块。它们提供几乎相同的功能,但语法不同

当只安装了“正确”的软件包时,一切正常。但是如果两个包都安装了,使用import rtmidi加载“错误”的模块,我的程序就会崩溃。让它重新工作的唯一方法是卸载这两个软件包,然后重新安装正确的软件包。当然,由于用户可能会依赖其他模块来执行其他程序,我不能期望他们这样做

尝试用rtmidi.__name__标识模块会对两个包产生相同的结果

所以我的问题是,我该如何解决这个名称冲突问题?有没有最佳实践方法来处理这个问题


Tags: 模块方法用户nameimport程序功能名称
3条回答

我想你应该看看你不想要的,然后删除它,然后再尝试导入它。 您可以在站点包文件夹中找到包

不能同时安装它们(如果依赖默认的pip行为)。在这里,我将用两个简单的纯Python包来演示,它们有一个导入名称冲突:coverallspython-coveralls

# in a fresh environment
pip install python-coveralls
python -c "import coveralls; print(dir(coveralls)); print(coveralls.__file__)"
# ['__author__', '__builtins__', '__cached__', '__classifiers__', '__copyright__', 
# '__doc__', '__docformat__', '__file__', '__license__', '__loader__', '__name__', 
# '__package__', '__path__', '__spec__', '__version__', 'parse_args', 'wear']
# /path/to/lib/python3.8/site-packages/coveralls/__init__.py

这些是python-coveralls的内容。请注意,实际的__init__.py文件位于site-packages/coveralls/目录中

pip install coveralls
python -c "import coveralls; print(dir(coveralls)); print(coveralls.__file__)"
# ['Coveralls', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', 
# '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', 
# 'api', 'exception', 'git', 'reporter', 'version']
# /path/to/lib/python3.8/site-packages/coveralls/__init__.py

这些是coveralls的内容python-coveralls已被覆盖。请注意,这是相同的文件。旧__init__.py里的任何东西都不见了

pip uninstall coveralls
python -c "import coveralls; print(dir(coveralls)); print(coveralls.__file__)"
# ['__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', 
# '__spec__']
# None

那里仍然有一个包的幽灵,但是它的内容不见了(除了所有包内部都有的东西)。注意,文件现在是None:这个包没有__init__.py文件

您也可以通过运行pip uninstall python-coveralls然后重新安装任意程序来解除对环境的阻塞

解决方案

您必须要求您的用户只使用您需求中的软件包,但这就是我们使用虚拟环境的原因。或者,想要直接使用另一个包的用户可以使用^{} option to ^{}更改安装位置(以及加载时使用的名称)(但这对使用另一个库的其他应用程序没有帮助)

实际上,最好将此作为安装过程的一部分。您需要(a)告诉用户需要安装什么要求;或者(b)有一些自动化的过程来满足正确的需求

我认为最佳实践是(b)。一种常见的方法是使用setuptools,并为setup函数定义install_requires参数。这是指PyPI上的包名,而不是导入名The setuptools quickstart是一个很好的起点

以下内容基于hoefling的评论和dwhswenson的回答(后者很好地解释了问题所在)

  1. 将其标记给项目所有者,以便他们更改其模块的名称。从理论上讲,这是迄今为止最好的解决方案,因为它不仅可以解决您的问题,还可以将其他人从中解救出来,这符合模块开发人员的最佳利益。在实践中,它打开了一整罐蠕虫,让人们知道应该改哪个名字

  2. 或在项目中包含模块,也称为vendoring,并使用路径my_project/_vendor/the_module导入它(或在sys.path中首先添加该路径)。如果模块只是一个.py文件,并且许可证允许,这是解决问题的最简单方法。在我的例子中,这是不可行的,因为我需要的模块包括一些需要编译的C代码,包括整个源代码,然后要求用户编译它似乎是一个大问题。所以我必须这样做:

  3. 或告诉用户在项目的文件夹中安装模块。使用pip选项--target以及项目中的文件夹路径,您可以获得自己的模块副本,而不会覆盖系统。然后,您可以在导入之前检查该文件夹是否存在。如果是,则从那里导入模块,如果不是,则可以检查系统安装的模块是否正确,如果不正确,则提示用户

下面是我如何在项目中实现代码的一个粗略示例。它取代了import rtmidi

if os.path.isdir('my_project/rtmidi'):
    from .rtmidi.rtmidi import _rtmidi as rtmidi
else:
    try:
        import rtmidi
    except ImportError:
        print('Please run: pip3 install rtmidi')
        exit()
    try:
        # something that only works with the right module:
        test = rtmidi.MidiMessage() 
    except:
        print('Please run: pip3 install --target=./my_project/rtmidi rtmidi')
        exit()

注意:我应该只try导入本地文件夹模块,而不是使用条件语句,并使用路径my_project/_vendor/rtmidi

相关问题 更多 >