【问题标题】:Which is the better way to handle ImportError - Raise error or import chain?处理 ImportError - 引发错误或导入链的更好方法是什么?
【发布时间】:2018-01-28 08:13:02
【问题描述】:

问题

在python中遇到ImportError,是直接报错让用户安装,还是使用import chain?

说明

我在尝试使用 lxml 包解析 python 中的 xml 文件时遇到了这个问题。
在其官方文档中,它说:

如果您的代码仅使用 ElementTree API 并且不依赖于任何特定于 lxml.etree 的功能,您还可以使用以下导入链(的任何部分)作为原始 ElementTree 的后备:

try:
    from lxml import etree
    print("running with lxml.etree")
except ImportError:
    try:
        import xml.etree.cElementTree as etree
        print("running with cElementTree on Python 2.5+")
    except ImportError:
        ...

在我看来,导入替换是一个坏主意,因为:
如果你可以导入另一个库作为替代,它可能没有 lxml 的所有方法,那么 你的所有脚本只能基于所有包中的那些可用方法

那么导入最强大的包(例如这里的lxml)就没有意义了,我们可以直接导入功能最少的包,并节省大量代码。或者如果我们以后想使用额外的方法,我们应该直接引发 ImportError。

但是,正如Error handling when importing modules 中的回答,我发现这种方法似乎在 python 编程中经常使用:

根据在运行时导入的库来定义多级功能很有用。

但在我看来,多层次的功能只能通过不断检查是否导入了一个库来实现,这使得整个代码变得复杂和丑陋。

结果,我只是想知道为什么人们有时会使用这样的结构,而不是直接引发错误?

【问题讨论】:

    标签: python error-handling python-import


    【解决方案1】:

    先回答你的最后一个问题:

    在python中遇到ImportError,是直接报错让用户安装,还是使用import chain?

    您可以处理ImportErrors 的原因有很多:

    • 如果您的模块直接依赖于某个模块,则让错误发生。如果依赖项的安装不重要,一些库会通过有用的错误消息重新引发错误。
    • 如果您的模块试图用速度较慢的库替换具有相同 API 的较快库,则没有理由在屏幕上打印任何内容。
    • 如果您的模块希望存在某个库,但您只能找到一个速度明显较慢的库,则警告可能有助于让开发人员知道您的模块仍然可以运行,但速度不会达到应有的速度.

    现在回答您的其他问题:

    那么导入最强大的包(例如这里的lxml)就没有意义了,我们可以直接导入功能最少的包,并节省大量代码。

    lxml.etreeElementTreecElementTree 的具体情况下,这三个都实现了相同的API。它们是彼此的替代品。 ElementTree 是纯 Python 并且将始终有效,但 cElementTree 通常存在并且速度更快。 lxml.etree 更快,但它是一个外部模块。

    这样想:

    try:
        import super_fast_widget as widget
    except ImportError:
        try:
            import fast_widget as widget
        except ImportError:
            import slow_widget as widget
    

    从您的代码的角度来看,widget 将始终以相同的方式工作,无论实际最终导入哪个库,因此最好尝试导入最快的实现,如果您关心性能,则使用较慢的实现。

    您是正确的,如果您允许后备库,您将无法充分利用 所有 lxml 的功能。这就是为什么使用lxml.etree 而不仅仅是lxml。它有意模仿其他两个库的 API。

    这里有一个来自 Django 代码库的类似示例:

    # Use the C (faster) implementation if possible
    try:
        from yaml import CSafeLoader as SafeLoader
        from yaml import CSafeDumper as SafeDumper
    except ImportError:
        from yaml import SafeLoader, SafeDumper
    

    Python 在内部为许多内置模块执行此操作。有一个较慢的纯 Python 版本用作较快的 C 版本的后备版本。

    但是,正如导入模块时的错误处理中所回答的那样,我发现这种方法似乎在 python 编程中经常使用:

    您的 lxml.etree 示例将较慢的库替换为较快的库。链接的示例代码定义了一个通用的跨平台接口 (getpass) 到一堆库,这些库都做同样的事情(提示您输入密码)。作者处理了ImportErrors,因为根据您的操作系统,这些单独的模块可能不存在。

    您可以用if platform.system() == 'Windows' 和类似的代码替换一些try 块,但即使在单个操作系统中也可能有更好的模块来执行相同的任务,因此try 块只是简化了它。最后getpass 仍然使用完全相同的 API 提示用户输入密码,这才是你真正关心的。

    【讨论】:

    • 有道理,所以导入链主要用于考虑跨平台和效率的原因,而不是多级功能。 (我将多级功能理解为基于在运行时导入的不同库来执行不同的工作,例如,如果已导入库,我希望获得更好的结果,如果没有,我希望获得更基本的目标)如果我想实现多级功能我的意思是(例如,我想使用特定于 lxml.etree 的方法),最好提出错误?
    • @Cielo:你能解释一下“多级功能”是什么意思吗?
    • 多层次的功能,我的意思是“如果导入成功,让它更漂亮,否则满足基本要求”。例如,如果导入 lxml,请尝试更正不匹配的标签,否则遇到相同问题时会报错。我不确定这个术语是否与 Dive into Python 中的含义相同。
    • @Cielo:那时我认为由您决定您希望应用程序如何运行。我通常不喜欢这样的可选运行时依赖项,因为我发现在安装模块时能够充分利用它更合理,但总是有例外。
    【解决方案2】:

    我通常使用导入链,因为输出更可控。

    引发错误

    Traceback (most recent call last):
      File "core.py", line 1, in <module>
    ImportError: <error description>
    

    进口链

    i Importing "lxml.etree"
    x Error Importing "lxml.etree"
    i Importing "xml.etree.cElementTree" on Python 2.5+
    x Error Importing "xml.etree.cElementTree" on Python 2.5+
    i Please Install "lxml.etree" or "xml.etree.xElementTree" on Python 2.5+
    i Exit with code 1
    

    【讨论】:

      猜你喜欢
      • 2021-08-07
      • 2021-01-29
      • 1970-01-01
      • 2016-06-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-24
      相关资源
      最近更新 更多