[Python] Les instructions d'importation doivent-elles toujours figurer en haut d'un module?


Answers

Mettre l'instruction import dans une fonction peut empêcher les dépendances circulaires. Par exemple, si vous avez deux modules, X.py et Y.py, et qu'ils doivent tous deux s'importer, cela provoquera une dépendance circulaire lorsque vous importerez l'un des modules provoquant une boucle infinie. Si vous déplacez l'instruction d'importation dans l'un des modules, il n'essaiera pas d'importer l'autre module avant l'appel de la fonction, et ce module sera déjà importé, donc pas de boucle infinie. Lisez ici pour plus d'informations - effbot.org/zone/import-confusion.htm

Question

Le PEP 08 stipule:

Les imports sont toujours placés en haut du fichier, juste après les commentaires et les docstrings du module, et avant les globals et les constantes du module.

Cependant, si la classe / méthode / fonction que j'importe est seulement utilisée dans de rares cas, il est sûrement plus efficace de faire l'importation quand cela est nécessaire.

N'est-ce pas:

class SomeClass(object):

    def not_often_called(self)
        from datetime import datetime
        self.datetime = datetime.now()

plus efficace que cela?

from datetime import datetime

class SomeClass(object):

    def not_often_called(self)
        self.datetime = datetime.now()



C'est comme beaucoup d'autres optimisations - vous sacrifiez une certaine lisibilité pour la vitesse. Comme John l'a mentionné, si vous avez fait vos devoirs de profilage et que vous avez trouvé que c'est un changement assez important et que vous avez besoin de la vitesse supplémentaire, alors allez-y. Il serait probablement bon de mettre une note avec toutes les autres importations:

from foo import bar
from baz import qux
# Note: datetime is imported in SomeClass below



Je n'aspire pas à fournir une réponse complète, car d'autres l'ont déjà fait très bien. Je veux juste mentionner un cas d'utilisation quand je trouve particulièrement utile d'importer des modules dans les fonctions. Mon application utilise des paquets python et des modules stockés à certains endroits en tant que plugins. Au démarrage de l'application, l'application parcourt tous les modules de l'emplacement et les importe, puis regarde à l'intérieur des modules et trouve des points de montage pour les plugins (dans mon cas c'est une sous-classe d'une classe de base ayant un ID) il les enregistre. Le nombre de plugins est grand (maintenant des dizaines, mais peut-être des centaines dans le futur) et chacun d'eux est utilisé assez rarement. Avoir des importations de bibliothèques tierces en haut de mes modules plugin était un peu pénalisé lors du démarrage de l'application. En particulier, certaines bibliothèques tierces sont lourdes à importer (par exemple, l'importation d'intrigues tente même de se connecter à Internet et de télécharger quelque chose qui ajoutait environ une seconde au démarrage). En optimisant les imports (en ne les appelant que dans les fonctions où ils sont utilisés) dans les plugins, j'ai réussi à réduire le démarrage de 10 secondes à 2 secondes. C'est une grosse différence pour mes utilisateurs.

Donc, ma réponse est non, ne mettez pas toujours les importations au sommet de vos modules.




Curt fait un bon point: la deuxième version est plus claire et échouera au moment du chargement plutôt que plus tard, et de façon inattendue.

Normalement, je ne m'inquiète pas de l'efficacité du chargement des modules, car (a) est assez rapide, et (b) ne se produit généralement qu'au démarrage.

Si vous devez charger des modules lourds à des moments inattendus, il est probablement plus judicieux de les charger dynamiquement avec la fonction __import__ , et assurez-vous d'attraper les exceptions ImportError , et de les gérer de manière raisonnable.




C'est un compromis, que seul le programmeur peut décider de faire.

Le cas 1 économise de la mémoire et du temps de démarrage en n'important pas le module datetime (et en faisant l'initialisation dont il a besoin) jusqu'à son utilisation. Notez que faire l'importation «seulement quand on l'appelle» signifie aussi le faire «chaque fois qu'il est appelé», de sorte que chaque appel après le premier entraîne toujours le surcoût supplémentaire de l'importation.

Le cas 2 permet d'économiser du temps d'exécution et de la latence en important au préalable la date et l'heure de sorte que not_often_called () revienne plus rapidement lors de l'appel, mais aussi en n'impliquant pas le coût d'une importation sur chaque appel.

Outre l'efficacité, il est plus facile de voir les dépendances des modules dès le début si les instructions d'importation sont ... à l'avant. En les cachant dans le code peut rendre plus difficile de trouver facilement quels modules quelque chose dépend.

Personnellement, je suis généralement le PEP à l'exception des choses comme les tests unitaires et tels que je ne veux pas toujours chargé parce que je sais qu'ils ne vont pas être utilisés, sauf pour le code de test.




En plus des excellentes réponses déjà données, il convient de noter que le placement des importations n'est pas seulement une question de style. Parfois, un module a des dépendances implicites qui doivent d'abord être importées ou initialisées, et une importation de niveau supérieur peut entraîner des violations de l'ordre d'exécution requis.

Ce problème survient souvent dans l'API Python d'Apache Spark, où vous devez initialiser le SparkContext avant d'importer des paquets ou des modules pyspark. Il est préférable de placer les importations de pyspark dans un périmètre où le SparkContext est garanti disponible.




La plupart du temps, cela serait utile par souci de clarté et de bon sens, mais ce n'est pas toujours le cas. Vous trouverez ci-dessous quelques exemples de circonstances dans lesquelles les importations de modules peuvent se trouver ailleurs.

Premièrement, vous pourriez avoir un module avec un test unitaire du formulaire:

if __name__ == '__main__':
    import foo
    aa = foo.xyz()         # initiate something for the test

Deuxièmement, vous pourriez avoir besoin d'importer conditionnellement un module différent à l'exécution.

if [condition]:
    import foo as plugin_api
else:
    import bar as plugin_api
xx = plugin_api.Plugin()
[...]

Il y a probablement d'autres situations où vous pourriez placer des importations dans d'autres parties du code.




Links