Tengo clases que requieren dependencias para ser instanciadas, pero por lo demás son opcionales. Me gustaría importar perezosamente las dependencias y fallar al crear una instancia de la clase si no están disponibles. Tenga en cuenta que estas dependencias no son necesarias a nivel de paquete (de lo contrario, serían obligatorias a través de las herramientas de configuración). Actualmente tengo algo como esto:
class Foo: def __init__(self): try: import module except ImportError: raise ModuleNotFoundError("...") def foo(self): import module Debido a que este patrón de prueba/excepto es común, me gustaría abstraerlo en un importador perezoso. Idealmente, si el module está disponible, no necesitaré importarlo nuevamente en Foo.foo , por lo que me gustaría que el module esté disponible una vez que se haya importado en __init__ . Intenté lo siguiente, que llena globals() y no crea una instancia de la clase si numpy no está disponible, pero contamina el espacio de nombres global.
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,]) Podría instanciar el módulo fuera de la clase y apuntar al módulo importado si la importación no falla, pero eso es lo mismo que el enfoque globals() . Alternativamente, lazy_import podría devolver el mod y podría llamarlo cada vez que se necesite el módulo, pero esto equivale a importarlo en todas partes como antes.
¿Hay una mejor manera de manejar esto?
Pandas en realidad tiene una función import_optional_dependency que puede ser un buen ejemplo ( enlace GitHub ) como se usa en SQLAlchemyEngine ( enlace GitHub )
Sin embargo, esto solo se usa durante la clase __init__ para obtener un error significativo ( raise ImportError(...) de forma predeterminada!) importar correctamente en cualquier lugar si existen, pero no funcionan o se prueban explícitamente o incluso son una importación local accidental)
Consideraría hacer algo similar, y no molestarme en tener un manejo especial o solo hacerlo en __init__ (y quizás solo en algunos casos en los que esté interesado en la versión, etc.) y, de lo contrario, simplemente importar donde sea necesario
class Foo(): def __init__(self, ...): import bar # only tests for existence def usebar(self, value): import bar bar.baz(value) Posiblemente podría asignar a una propiedad de la clase, pero esto puede causar algunos problemas o confusión (ya que la importación ya debería estar disponible en globals una vez importada)
class Foo(): def __init__(self, ...): import bar self.bar = bar def usebar(self, value): self.bar.baz(value)Le di una prueba rápida con un envoltorio, parece funcionar bien:
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())Editar: uno más avanzado que funciona con cualquier módulo y garantiza que sea un módulo en caso de que se haya configurado en otra cosa.
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())