在 Python 中导入很少使用的包的最佳实践

Best practices for importing rarely used package in Python

我的 Python 包的一些功能依赖于外部库。这是一个非 Python 包,可能很难安装,所以我希望用户仍然可以使用我的包,但在使用依赖于此非 [=35= 的任何功能时它会失败] 包裹。

这个的标准做法是什么?我只能在使用它的方法中导入非Python包,但我真的很讨厌这样做

我当前的设置:

myInterface.py
myPackage/
--classA.py
--classB.py

接口脚本 myInterface.py 导入 classAclassB 并且 classB 导入非 Python 包。如果导入失败,我会打印一条警告。如果调用了 myMethod 并且未安装软件包,下游将出现一些错误,但我不会在任何地方捕获它,也不会警告用户。

每次调用接口脚本时都会导入

classB,所以我不会在那里出现任何失败,这就是我包含 pass 的原因。就像我上面说的,我可以在方法内部导入并让它在那里失败,但我真的很喜欢将所有导入保存在一个地方。

来自classB.py

try:
    import someWeirdPackage
except ImportError:
    print("Cannot import someWeirdPackage")
    pass

class ClassB():
    ...
    def myMethod():
        swp = someWeirdPackage()
        ...

我不确定在这种情况下是否有任何最佳实践,但如果不支持,我会重新定义该函数:

def warn_import():
    print("Cannot import someWeirdPackage")

try:
    import someWeirdPackage
    external_func = someWeirdPackage
except ImportError:
    external_func = warn_import


class ClassB():
    def myMethod(self):
        swp = external_func()


b = ClassB()
b.myMethod()

如果您只导入一个外部库,我会按照以下思路进行操作:

try:
    import weirdModule
    available = True
except ImportError:
    available = False

def func_requiring_weirdmodule():
    if not available:
        raise ImportError('weirdModule not available')
    ...

条件和错误检查仅在您想提供更多描述性错误时才需要。如果不是,您可以省略它并让 python 在尝试调用非导入模块时抛出相应的错误,就像您在当前设置中所做的那样。

如果确实有多个函数使用 weirdModule,您可以将检查包装到一个函数中:

def require_weird_module():
    if not available:
        raise ImportError('weirdModule not available')

def f1():
    require_weird_module()
    ...

def f2():
    require_weird_module()
    ...

另一方面,如果你有多个库需要被不同的函数导入,你可以动态加载它们。虽然看起来不怎么好看,但是python缓存起来也没什么问题。我会使用 importlib

import importlib

def func_requiring_weirdmodule():
    weirdModule = importlib.import_module('weirdModule')

同样,如果您的多个函数导入复杂的外部模块,您可以将它们包装成:

def import_external(name):
    return importlib.import_module(name)

def f1():
    weird1 = import_external('weirdModule1')


def f2():
    weird2 = import_external('weirdModule2')

最后,您可以创建一个处理程序来防止两次导入同一模块,类似于:

class Importer(object):

    __loaded__ = {}

    @staticmethod
    def import_external(name):
        if name in Importer.__loaded__:
            return Importer.__loaded__[name]
        mod = importlib.import_module(name)
        Importer.__loaded__[name] = mod
        return mod

def f1():
    weird = Importer.import_external('weird1')


def f2():
    weird = Importer.import_external('weird1')

尽管我很确定 importlib 会在场景中进行缓存,而您实际上并不需要手动缓存。


总之,虽然确实难看,但是在python中动态导入模块是没有错的。事实上,很多图书馆都依赖于此。另一方面,如果它只是针对 3 个方法访问 1 个外部函数的特殊情况,请使用您的方法或我的第一个方法,以防您无法添加自定义 sception 处理。

您可以为这两种情况创建两个单独的 类。第一个将在包存在时使用。第二个将在包不存在时使用。

class ClassB1():
    def myMethod(self):
        print("someWeirdPackage exist")
        # do something

class ClassB2(ClassB1):
    def myMethod(self):
        print("someWeirdPackage does not exist")
        # do something or raise Exception

try:    
    import someWeirdPackage
    class ClassB(ClassB1):
        pass
except ImportError:
    class ClassB(ClassB2):
        pass

您也可以使用下面给出的方法来解决您面临的问题。

class UnAvailableName(object):

    def __init__(self, name):
        self.target = name

    def __getattr_(self, attr):
        raise ImportError("{} is not available.".format(attr))


try:
    import someWeirdPackage
except ImportError:
    print("Cannot import someWeirdPackage")
    someWeirdPackage = someWeirdPackage("someWeirdPackage")

class ClassB():
    def myMethod():
        swp = someWeirdPackage.hello()

a = ClassB()
a.myMethod()