【问题标题】:How do I automatically install missing python modules? [duplicate]如何自动安装缺少的 python 模块? [复制]
【发布时间】:2011-09-01 12:43:28
【问题描述】:

我希望能够写作:

try:
    import foo
except ImportError:
    install_the_module("foo")

处理这种情况的推荐/惯用方法是什么?

我见过很多脚本只是简单地打印一个错误或警告,通知用户缺少模块,并且(有时)提供有关如何安装的说明。但是,如果我知道该模块在PyPI 上可用,那么我肯定可以更进一步启动安装过程。没有?

【问题讨论】:

  • 理论上很好,但在现实中却很痛苦。只需让用户自己安装包或在将包创建为依赖项时提供它们。
  • @JakobBowyer - 你的第一句话总结了人们得到报酬去做的大多数事情——如果某件事是个好主意并且不难做到,那么它已经完成了,不需要任何人付钱给别人通过 PITA。我在工作中得到报酬,编写了一个脚本,该脚本可以自动部署服务器并且可以在没有任何用户交互的情况下运行,因此我需要自动处理尚未安装 Python 模块的情况。现在世界可以从我得到报酬的工作中受益 - 我在下面记录了它:stackoverflow.com/a/25643988/901641
  • 5 次投票并不能使你的 python 风格符合 PEP8
  • @CoreyGoldberg - 请允许我引用 PEP8 的话,因为您似乎非常重视它:“愚蠢的一致性是小聪明的妖精”。当我不确定如何格式化一行时,我会看看 PEP8 是怎么说的。否则,我的目标是最大的可读性。如果您认为我做得不够,请随时编辑我的代码。
  • @CoreyGoldberg Python 不敢苟同。 “美丽胜于丑陋。/显式胜于隐式。/简单胜于复杂。” — “Python 之禅”,第 1-3 行

标签: python module pypi


【解决方案1】:
try:
    import foo
except ImportError:
    sys.exit("""You need foo!
                install it from http://pypi.python.org/pypi/foo
                or run pip install foo.""")

不要碰用户的安装。

【讨论】:

  • 这似乎是多余的,因为如果foo 丢失,用户已经收到错误ModuleNotFoundError: No module named 'foo'
【解决方案2】:

安装问题不在源代码范围内!

您在包的setup.py 中正确定义了依赖项 使用install_requires 配置。

这就是要走的路...由于ImportError而安装一些东西 有点奇怪和可怕。不要这样做。

【讨论】:

  • 这在 setup.py 本身需要所需模块的情况下无济于事。是否存在 setup() 提供的某种钩子,可以允许它通过 setup_requires 安装安装时依赖项,然后在对 setup() 的同一调用中使用新安装的依赖项中定义的命令类,而无需添加过多的样板为每个使用该依赖项的包设置 setup.py?
  • 我添加了a new question 来解决我的特定用例。
  • 我已经用 python 编程了十年,但由于多种原因从未使用过 setup.py。这最终会通过 pip 安装它吗?如果我在一个高度安全的网络上并且只能通过 RPM 安装它怎么办?我发现这个 python-reportlab.x86_64 RPM 显然是由 RH 和 CentOS 分发的,所以我必须这样安装它……有什么建议吗?
【解决方案3】:

这是我整理的解决方案,我称之为pyInstall.py。它实际上检查模块是否已安装,而不是依赖ImportError(在我看来,使用if 而不是try/except 来处理这个问题看起来更简洁)。

我在 2.6 和 2.7 版本下使用过它...如果我不想将 print 作为函数处理,它可能会在旧版本中工作...而且我认为它会在 3.0 版本中工作+ 但我从未尝试过。

另外,正如我在 getPip 函数的 cmets 中所指出的,我认为该特定函数在 OS X 下无法运行。

from __future__ import print_function
from subprocess import call

def installPip(log=print):
    """
    Pip is the standard package manager for Python. Starting with Python 3.4
    it's included in the default installation, but older versions may need to
    download and install it. This code should pretty cleanly do just that.
    """
    log("Installing pip, the standard Python Package Manager, first")
    from os     import remove
    from urllib import urlretrieve
    urlretrieve("https://bootstrap.pypa.io/get-pip.py", "get-pip.py")
    call(["python", "get-pip.py"])

    # Clean up now...
    remove("get-pip.py")

def getPip(log=print):
    """
    Pip is the standard package manager for Python.
    This returns the path to the pip executable, installing it if necessary.
    """
    from os.path import isfile, join
    from sys     import prefix
    # Generate the path to where pip is or will be installed... this has been
    # tested and works on Windows, but will likely need tweaking for other OS's.
    # On OS X, I seem to have pip at /usr/local/bin/pip?
    pipPath = join(prefix, 'Scripts', 'pip.exe')

    # Check if pip is installed, and install it if it isn't.
    if not isfile(pipPath):
        installPip(log)
        if not isfile(pipPath):
            raise("Failed to find or install pip!")
    return pipPath

def installIfNeeded(moduleName, nameOnPip=None, notes="", log=print):
    """ Installs a Python library using pip, if it isn't already installed. """
    from pkgutil import iter_modules

    # Check if the module is installed
    if moduleName not in [tuple_[1] for tuple_ in iter_modules()]:
        log("Installing " + moduleName + notes + " Library for Python")
        call([getPip(log), "install", nameOnPip if nameOnPip else moduleName])

以下是一些用法示例:

from datetime  import datetime
from pyInstall import installIfNeeded

# I like to have my messages timestamped so I can get an idea of how long they take.
def log(message):
    print(datetime.now().strftime("%a %b %d %H:%M:%S") + " - " + str(message))

# The name fabric doesn't really convey to the end user why the module is needed,
# so I include a very quick note that it's used for SSH.
installIfNeeded("fabric", notes = " (ssh)", log = log)

# SoftLayer is actually named softlayer on pip.
installIfNeeded("SoftLayer", "softlayer", log = log)

编辑:获取 pipPath 的一种更跨平台的方式是:

from subprocess import Popen, PIPE
finder = Popen(['where' if isWindows() else 'which', 'pip'], stdout = PIPE, stderr = PIPE)
pipPath = finder.communicate()[0].strip()

这假设pip 是/将安装在系统路径上。它在非 Windows 平台上往往相当可靠,但在 Windows 上,使用我原始答案中的代码可能会更好。

【讨论】:

    【解决方案4】:

    冒着反对票的风险,我想建议一个快速破解。请注意,我完全同意接受的答案,应该在外部管理依赖项。

    但对于您绝对需要破解自包含的东西的情况,您可以尝试以下操作:

    import os
    
    try:
      import requests
    except ImportError:
      print "Trying to Install required module: requests\n"
      os.system('python -m pip install requests')
    # -- above lines try to install requests module if not present
    # -- if all went well, import required module again ( for global access)
    import requests
    

    【讨论】:

    • os.system 已弃用...使用子进程模块
    • 最好使用子进程模块,但不推荐使用 os.system
    • 这是一个 hack,但该死的好。谢谢。
    • 使用subprocess 的解决方案可以在stackoverflow.com/a/58040520 找到。
    • 感谢您的风险,当您没有时间或项目要求制作软件包(egg 或 tarball)时,这非常有用,当您在容器上重新安装时也适用于 CI/CD /VM 的/Docket。
    猜你喜欢
    • 1970-01-01
    • 2012-01-01
    • 2013-10-02
    • 1970-01-01
    • 2018-03-26
    • 2012-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多