Python导入不常用包的最佳实践

3

我的Python包的一些功能依赖于外部库。这是一个非Python包,安装起来可能比较困难,因此我希望用户仍然能够使用我的包,但在使用任何依赖于这个非Python包的函数时会失败。

这种情况下的标准做法是什么? 我可以只在使用它的方法中导入非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()
        ...

如果classBclassB.py中的全部内容,那么我不理解pass的目的。否则,我会违背纯粹性并选择实用性(在class定义内部导入它)。 - Ma0
ClassBclassB.py 中唯一的类,但只有 2-3 个方法依赖于 someWeirdPackage,因此我使用了 pass,以便其他人可以使用该类中的其他方法。 - Sal
我不知道你所编写的代码是否有这种可能性,但许多软件包都有以更迂回的方式编写所需功能的方法,并且被打包为方便使用。也许如果你只是从 someWeirdPackage 中使用了几个功能,那么有一种方法可以自己编写该功能,而不必导入整个软件包? - Davy M
@DavyM 我肯定做不到那个 :) ,我正在导入斯坦福统计解析器:https://nlp.stanford.edu/software/lex-parser.shtml - Sal
4个回答

5
如果你只需要引入一个外部库,我建议你采用以下方式:
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个外部函数的特殊情况,请使用你的方法或者我的第一个方法,以防需要添加自定义异常处理。

3

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

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()

0

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

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

0
你也可以使用下面提供的方法来克服你所面临的问题。
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()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接