¿Es seguro detectar ImportError al intentar importar módulos opcionales?

10

Por lo general, veo este patrón al menos una vez en cada proyecto de Python en el que trabajo. Por ejemplo, en un proyecto Django, esto a menudo se agrega al final del archivo de configuración base:

try:
  from .local_settings import *
except ImportError:
  pass

También:

try:
  import simplejson as json
except ImportError:
  import json

Sin embargo, esto siempre me ha molestado un poco; ¿Qué pasa si el módulo se importa con éxito, pero luego se dispara ImportError? Por ejemplo, en el primer ejemplo, el local_settingsmódulo existe, pero luego local_settingstrata de importar un módulo inexistente.

¿Es esta la forma más segura de importar un módulo opcional, hay una mejor manera de lograr esta funcionalidad, o depende del contexto / uso (y si es así, ¿cuáles son las pautas para decidir cuándo usar este enfoque)?


fuente

Respuestas:

10

En general, se debe suponer que la dependencia opcional que está importando puede funcionar por sí sola. Hay poca diferencia entre intentar importar una dependencia opcional que falta y una que no se puede importar porque falta una dependencia transitiva requerida .

En otras palabras, ¿por qué su programa debería preocuparse por simplejsonno estar disponible porque no está instalado o porque una dependencia de simplejsonno está instalada? De cualquier manera no puedes usar simplejson.

Para dependencias opcionales, depende del instalador del paquete asegurarse de que una dependencia esté instalada correctamente, incluidas las dependencias transitivas.

Para algo así local_settings, de hecho existe un riesgo (pequeño) de que ImportErrorse enmascara un transitivo . Siempre puede registrar la ImportErrorexcepción detectada en el nivel DEBUG o similar para facilitar la revisión de lo que pudo haber causado la excepción:

try:
    from .local_settings import *
except ImportError:
    log.debug('local_settings failed to import', exc_info=True)

El loggingpaquete incluirá la información de excepción en el registro para una inspección posterior cuando establezca exc_infoen verdadero.

Otra opción es emitir una advertencia con el warningsmódulo , la biblioteca estándar tiene una ImportWarningclase para esto:

import warnings

try:
    from .local_settings import *
except ImportError:
    warnings.warn('local_settings failed to import', ImportWarning)
Martijn Pieters
fuente
Sugiero usar ImportWarning, que se menciona en documentos oficiales junto o en lugar de iniciar sesión.
Eray Erdin
1
@ErayErdin: eso depende del caso de uso. Si el módulo se está importando como una optimización de rendimiento, puede argumentar que una advertencia es solo ruido, ciertamente no es un error como ImportWarningsugiere la documentación .
Martijn Pieters
3

Esto generalmente es seguro, suponiendo que sus módulos estén libres de efectos secundarios de tiempo de importación.

Si un módulo importado genera alguna excepción (no solo ImportError) en el momento de la importación, se elimina desys.modules . Esto significa que si algún otro código intenta importar el módulo, no obtendrá un módulo parcialmente inicializado. En cambio, Python intentará cargar el módulo nuevamente, y presumiblemente fallará ImportErrornuevamente.

Esto puede causar problemas si sus módulos tienen efectos secundarios de tiempo de importación, ya que esos efectos no se deshacen. En particular, si el módulo infractor importa con éxito otro módulo, este último no se elimina sys.modules. Este efecto secundario en particular rara vez es un problema, pero algunos efectos secundarios pueden ser más problemáticos.

Kevin
fuente