简体   繁体   English

如何在 python 中懒惰地导入模块?

[英]How can I lazily import a module in python?

I have classes which require dependencies in order to be instantiated but are otherwise optional.我有需要依赖项才能被实例化的类,但在其他方面是可选的。 I'd like to lazily import the dependencies and fail to instantiate the class if they aren't available.我想懒惰地导入依赖项,如果 class 不可用,则无法实例化它们。 Note that these dependencies are not required at the package level (otherwise they'd be mandatory via setuptools).请注意,package 级别不需要这些依赖项(否则它们将通过 setuptools 强制执行)。 I currently have something like this:我目前有这样的事情:

class Foo:
    def __init__(self):
        try:
            import module
        except ImportError:
            raise ModuleNotFoundError("...")

    def foo(self):
        import module

Because this try/except pattern is common, I'd like to abstract it into a lazy importer.因为这种 try/except 模式很常见,所以我想将它抽象为一个惰性导入器。 Ideally if module is available, I won't need to import it again in Foo.foo so I'd like module to be available once it's been imported in __init__ .理想情况下,如果module可用,我不需要在Foo.foo中再次导入它,所以我希望module__init__中导入后可用。 I've tried the following, which populates globals() and fails to instantiate the class if numpy isn't available, but it pollutes the global namespace.我尝试了以下方法,如果numpy不可用,它会填充globals()并且无法实例化 class ,但它会污染全局命名空间。

def lazy_import(name, as_=None):
    # Doesn't handle error_msg well yet
    import importlib
    mod = importlib.import_module(name)
    if as_ is not None:
        name = as_
    # yuck...
    globals()[name] = mod

class NeedsNumpyFoo:
    def __init__(self):
        lazy_import("numpy", as_="np")

    def foo(self):
        return np.array([1,2,])

I could instantiate the module outside the class and point to the imported module if import doesn't fail, but that is the same as the globals() approach.如果导入没有失败,我可以在 class 之外实例化模块并指向导入的模块,但这与globals()方法相同。 Alternatively lazy_import could return the mod and I could call it whenever the module is needed, but this is tantamount to just importing it everywhere as before.或者lazy_import可以返回mod ,我可以在需要模块时调用它,但这无异于像以前一样在任何地方导入它。

Is there a better way to handle this?有没有更好的方法来处理这个?

Pandas actually has a function import_optional_dependency which may make a good example ( link GitHub ) as used in SQLAlchemyEngine ( link GitHub ) Pandas actually has a function import_optional_dependency which may make a good example ( link GitHub ) as used in SQLAlchemyEngine ( link GitHub )

However, this is only used during class __init__ to get a meaningful error ( raise ImportError(...) by default,) or warn about absence or old dependencies (which is likely a more practical use of it, as older or newer dependencies may import correctly anywhere if they exist, but not work or be explicitly tested against or even be an accidental local import)但是,这仅在 class __init__期间使用以获得有意义的错误(默认为raise ImportError(...) )或警告缺少或旧的依赖项(这可能是更实际的使用它,因为较旧或较新的依赖项可能如果它们存在,则可以在任何地方正确导入,但不能正常工作或被明确测试甚至是意外的本地导入)

I'd consider doing similarly, and either not bother to have special handling or only do it in the __init__ (and then perhaps only for a few cases where you're interested in the version, etc.) and otherwise simply import where needed我会考虑做类似的事情,要么不费心进行特殊处理,要么只在__init__中进行(然后可能仅适用于您对版本感兴趣的少数情况等),否则只需在需要的地方导入

class Foo():
    def __init__(self, ...):
        import bar  # only tests for existence

    def usebar(self, value):
        import bar
        bar.baz(value)

Plausibly you could assign to a property of the class, but this may cause some trouble or confusion (as the import should already be available in globals once imported)有可能您可以分配给 class 的属性,但这可能会导致一些麻烦或混乱(因为一旦导入,导入应该已经在globals中可用)

class Foo():
    def __init__(self, ...):
        import bar
        self.bar = bar

    def usebar(self, value):
        self.bar.baz(value)

Gave it a quick test with a wrapper, seems to work fine:用包装器对其进行快速测试,似乎工作正常:

def requires_math(fn):
    def wrapper(*args, **kwargs):
        global math
        try:
            math
        except NameError:
            import math
        return fn(*args, **kwargs)
    return wrapper

@requires_math
def func():
    return math.ceil(5.5)

print(func())

Edit: More advanced one that works with any module, and ensures it is a module in case it's been set to something else.编辑:更高级的一个,适用于任何模块,并确保它是一个模块,以防它被设置为其他东西。

from types import ModuleType

def requires_import(*mods):
    def decorator(fn):
        def wrapper(*args, **kwargs):
            for mod in mods:
                if mod not in globals() or not isinstance(globals()[mod], ModuleType):
                    globals()[mod] = __import__(mod)
            return fn(*args, **kwargs)
        return wrapper
    return decorator

@requires_import('math', 'random')
def func():
    return math.ceil(random.uniform(0, 10))

print(func())

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM