【问题标题】:Check if package is imported from within the source tree检查包是否从源代码树中导入
【发布时间】:2019-09-17 22:28:03
【问题描述】:

用户应该通过 pip 安装我们的 python 包,或者它可以从 github repo 克隆并从源代码安装。出于多种原因,用户不应在源代码树目录中运行import Foo,例如缺少 C 扩展(numpy 有同样的问题:read here)。因此,我们想检查用户是否在源代码树中运行import Foo,但是如何在支持 Python 3 和 2 的情况下干净、高效、稳健地执行此操作?

编辑:注意这里的源代码树也被定义为下载代码的位置(例如,通过 git 或从源存档),它与安装代码的安装目录形成对比。

我们考虑了以下几点:

  • 检查setup.py 或其他文件,如PKG-INFO,它们应该只存在于源中。它不是那么优雅,并且检查文件的存在也不是很便宜,因为每次有人import Foo 时都会进行此检查。此外,没有什么可以阻止某人将 setup.py 放在其 lib/python3.X/site-packages/ 目录或类似目录中的源代码树之外。
  • 解析 setup.py 的内容作为包名,但这也增加了开销,而且解析起来也不是很干净。
  • 创建一个仅存在于源代码树中的虚拟标志文件。
  • 一些聪明但可能过于复杂且容易出错的想法,例如在安装过程中修改 Foo/__init__.py 以注意我们现在位于源代码树之外。

【问题讨论】:

  • 那么您希望向用户展示什么。如果用户从源代码树运行 import Foo,他会看到某种异常或警告吗? @Chris_Rands
  • @DeveshKumarSingh 是的,要么是 numpy 之类的异常引发,要么更可能是 if in_source_tree: warnings.warn(msg, CustomWarning) 之类的自定义警告消息
  • 我有一个包结构,其中import Foo; Foo.__file__ 将根据它的安装位置显示不同的路径,并且可以通过执行os.getcwd() 将其与源树路径进行比较,这样的方法为你工作@Chris_Rands!
  • @DeveshKumarSingh 注意——如果用户在安装目录中,例如lib/python3.X/site-packages/,然后Foo.__file__ 将匹配os.getcwd();如果用户实际上在源代码树中,它也会匹配。注意这里的源代码树也是下载代码的目录(通常通过 git clone)与安装代码的安装目录
  • 啊,为了解决这个问题,我总是可以做类似git rev-parse --git-dir 的事情,如果它是一个git repo,它将返回.git,否则它会抛出一个错误!而且这个命令只适用于通过 git 克隆的源目录,而不适用于其他任何地方!这会解决问题@Chris_Rands

标签: python package python-import


【解决方案1】:

由于您在 cmets 中提到 numpy 并希望像他们那样做但不完全理解它,我想我会分解它,看看您是否可以实施类似的过程。


__init__.py

您正在寻找的错误以 here 开头,这是您在 cmets 和答案中链接的内容,因此您已经知道了。它只是尝试导入__config__.py,如果它不存在或无法导入则失败。

    try:
        from numpy.__config__ import show as show_config
    except ImportError:
        msg = """Error importing numpy: you should not try to import numpy from
        its source directory; please exit the numpy source tree, and relaunch
        your python interpreter from there."""
        raise ImportError(msg)

那么 __config__.py 文件是从哪里来的,这有什么帮助呢?下面就让我们一起来看看吧……

setup.py

当安装包时,setup 被调用运行,然后它又执行一些configuration actions。这本质上是确保软件包正确安装而不是从下载目录运行的原因(我认为这是您想要确保的)。

这里的关键是这一行:

config.make_config_py() # installs __config__.py

misc_util.py

这是从distutils/misc_util.py 导入的,我们可以一直跟踪到here

    def make_config_py(self,name='__config__'):
        """Generate package __config__.py file containing system_info
        information used during building the package.
        This file is installed to the
        package installation directory.
        """
        self.py_modules.append((self.name, name, generate_config_py))

然后运行here,它在__config__.py 文件中写入一些系统信息和您的show() 函数。


总结
尝试导入 __config__.py 并失败,如果 setup.py 未运行,则会生成您想要引发的错误,这是触发正确创建该文件的原因。这不仅确保了文件检查正在完成,而且该文件只存在于安装目录中。在每次导入时导入一个附加文件仍然会产生一些开销,但无论您做什么,都会首先添加一些开销来进行此检查。


建议

我认为你可以在完成同样的事情的同时实现 numpy 正在做的更轻量级的版本。

删除distutils 子函数并在您的setup.py 文件中创建检查文件作为标准安装的一部分。它只会在安装后存在于已安装的目录中,并且永远不会存在于其他地方,除非用户伪造了它(在这种情况下,他们几乎可以绕过你尝试的任何东西)。

作为替代方案(不知道您的应用程序以及您的设置文件在做什么),您可能有一个通常会被导入的函数,它不是应用程序运行的关键,但可以使用(在 @987654340 中) @的情况下,函数是有关安装的信息,例如version()。不是将这些函数保留在您现在放置的位置,而是将它们作为创建的文件的一部分。然后您至少要加载一些本来要加载的东西无论如何,来自其他地方。

使用这种方法,无论如何您都在导入一些东西,这会产生一些开销,或者引发错误。我认为就引发错误的方法而言,因为它们没有在安装的目录之外工作,这是一种非常干净和直接的方法。无论您使用哪种方法,使用该方法都会产生一些开销,因此我将专注于保持开销低、简单且不会导致错误。

我不会做一些复杂的事情,比如解析安装文件或修改必要的文件,比如某处的__init__.py。我认为你是对的,这些方法更容易出错。

检查setup.py 是否存在可以工作,但我认为它不如尝试import 干净,因为import 已经优化为标准Python 函数。他们完成了类似的事情,但我认为实现numpy 风格会更直接。

【讨论】:

  • 谢谢-我已经检查了来源-我想我的问题不是我不明白,但我不确定这个解决方案是否过于复杂而无法在我们自己的包中实施。我们的setup.py 文件的结构与numpy 使用的完全不同,它们也有自己的distutils 模块。与我在问题中指出的解决方案相比,也许您对此解决方案的优缺点有看法?如果我们确实走numpy 路线(现在看来不太可能),我们希望它是轻量级的、独立的并且不会干扰当前的代码结构
  • 我在底部添加了一堆,因为我的评论空间用完了。基本上我认为numpy 路线,但简化将是最容易理解和实现的方法,而不是其他一些选项。我会优先考虑轻量级和易于理解的。
猜你喜欢
  • 1970-01-01
  • 2011-06-05
  • 1970-01-01
  • 1970-01-01
  • 2012-08-18
  • 1970-01-01
  • 1970-01-01
  • 2010-11-25
  • 1970-01-01
相关资源
最近更新 更多