É seguro capturar ImportError ao tentar importar módulos opcionais?

10

Normalmente, vejo esse padrão pelo menos uma vez em cada projeto Python em que trabalho. Por exemplo, em um projeto Django, isso geralmente é adicionado na parte inferior do arquivo de configurações básicas:

try:
  from .local_settings import *
except ImportError:
  pass

Além disso:

try:
  import simplejson as json
except ImportError:
  import json

Isso sempre me incomodou um pouco; e se o módulo for importado com sucesso, mas ele disparar um ImportError? Por exemplo, no primeiro exemplo, o local_settingsmódulo existe, mas local_settingstenta importar um módulo inexistente.

Essa é a maneira mais segura de importar um módulo opcional, existe uma maneira melhor de obter essa funcionalidade ou depende do contexto / uso (e se sim, quais são as diretrizes para decidir quando usar essa abordagem)?


fonte

Respostas:

10

Geralmente, deve-se supor que a dependência opcional que você está importando pode funcionar por conta própria. Há pouca diferença entre tentar importar uma dependência opcional ausente e uma que não pode ser importada porque está faltando uma dependência transitiva necessária .

Em outras palavras, por que seu programa precisaria se preocupar em simplejsonnão estar disponível porque não está instalado ou porque uma dependência de simplejsonnão está instalada? De qualquer maneira você não pode usar simplejson.

Para dependências opcionais, cabe ao instalador do pacote garantir que uma dependência seja instalada corretamente, incluindo dependências transitivas.

Para algo assim local_settings, há realmente um risco (pequeno) de que um transitivo ImportErrorseja mascarado. Você sempre pode registrar a ImportErrorexceção capturada no nível DEBUG ou similar para facilitar a revisão do que pode ter causado a exceção:

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

O loggingpacote incluirá as informações de exceção no log para inspeção posterior quando você definir exc_infocomo true.

Outra opção é emitir um aviso com o warningsmódulo , a biblioteca padrão possui uma ImportWarningclasse para isso:

import warnings

try:
    from .local_settings import *
except ImportError:
    warnings.warn('local_settings failed to import', ImportWarning)
Martijn Pieters
fonte
Sugiro usar o ImportWarning mencionado nos documentos oficiais ao lado ou em vez de registrá-lo.
Eray Erdin
1
@ErayErdin: isso depende do caso de uso. Se o módulo está sendo importado como uma otimização de desempenho, você pode argumentar que um aviso é apenas ruído, certamente não é um erro, como ImportWarningsugere a documentação .
Martijn Pieters
3

Isso geralmente é seguro, supondo que seus módulos estejam livres de efeitos colaterais no momento da importação.

Se um módulo importado gera alguma exceção (não apenas ImportError) no momento da importação, ele é removido desys.modules . Isso significa que, se qualquer outro código tentar importar o módulo, ele não receberá um módulo parcialmente inicializado. Em vez disso, o Python tentará carregar o módulo novamente e, presumivelmente, falhará com ImportErroro novamente.

Isso pode causar problemas se seus módulos tiverem efeitos colaterais no tempo de importação, pois esses efeitos não serão desfeitos. Em particular, se o módulo incorreto importar com êxito outro módulo, ele não será removido sys.modules. Esse efeito colateral específico raramente é um problema, mas alguns efeitos colaterais podem ser mais problemáticos.

Kevin
fonte