python subplot - Les instructions d'importation doivent-elles toujours figurer en haut d'un module?




title matplotlib (13)

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()

Answers

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.


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.


L'initialisation du module ne se produit qu'une seule fois - lors de la première importation. Si le module en question provient de la bibliothèque standard, vous l'importerez probablement d'autres modules de votre programme. Pour un module aussi répandu que datetime, il est également probable qu'il existe une dépendance pour un grand nombre d'autres bibliothèques standard. La déclaration d'importation coûterait très peu alors puisque l'initialisation du module aurait déjà eu lieu. Tout ce qu'il fait à ce stade lie l'objet module existant à la portée locale.

Ajoutez cette information à l'argument de lisibilité et je dirais qu'il est préférable d'avoir la déclaration d'importation au niveau du module.


J'ai adopté la pratique de mettre toutes les importations dans les fonctions qui les utilisent, plutôt que dans le haut du module.

Le bénéfice que j'obtiens est la capacité à refactoriser de manière plus fiable. Lorsque je déplace une fonction d'un module à un autre, je sais que la fonction continuera à fonctionner avec tout son héritage de tests intacts. Si j'ai mes importations en haut du module, quand je déplace une fonction, je trouve que je passe beaucoup de temps à faire les importations du nouveau module complet et minimal. Un IDE de refactoring peut rendre cela non pertinent.

Il y a une pénalité de vitesse comme mentionné ailleurs. J'ai mesuré cela dans ma demande et je l'ai trouvé insignifiant pour mes fins.

Il est également agréable de pouvoir voir toutes les dépendances de modules à l'avance sans recourir à la recherche (par exemple, grep). Cependant, la raison pour laquelle je m'intéresse aux dépendances de modules est généralement parce que j'installe, refactorise ou déplace un système entier comprenant plusieurs fichiers, pas seulement un seul module. Dans ce cas, je vais quand même effectuer une recherche globale pour m'assurer que j'ai les dépendances au niveau du système. Je n'ai donc pas trouvé d'importations mondiales pour faciliter ma compréhension d'un système dans la pratique.

Je place généralement l'importation de sys dans la if __name__=='__main__' , puis passe les arguments (comme sys.argv[1:] ) à une fonction main() . Cela me permet d'utiliser main dans un contexte où sys n'a pas été importé.


Il est intéressant que pas une seule réponse mentionné traitement parallèle jusqu'à présent, où il pourrait être nécessaire que les importations sont dans la fonction, lorsque le code de fonction sérialisé est ce qui est poussé vers d'autres noyaux, comme dans le cas de ipyparallel.


La première variante est en effet plus efficace que la seconde lorsque la fonction est appelée soit zéro, soit une fois. Avec la deuxième et les invocations suivantes, cependant, l'approche «importer tous les appels» est en réalité moins efficace. Voir ce lien pour une technique de chargement paresseux qui combine le meilleur des deux approches en faisant une "importation paresseuse".

Mais il y a des raisons autres que l'efficacité pourquoi vous pourriez préférer l'un à l'autre. Une approche rend beaucoup plus clair à quelqu'un qui lit le code quant aux dépendances que ce module a. Ils ont également des caractéristiques de défaillance très différentes - la première échouera au moment du chargement s'il n'y a pas de module "datetime" alors que la seconde n'échouera pas avant l'appel de la méthode.

Note ajoutée: Dans IronPython, les importations peuvent être un peu plus chères que dans CPython car le code est en train d'être compilé lors de son importation.


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


L'importation de module est assez rapide, mais pas instantanée. Cela signifie que:

  • Mettre les importations en haut du module est bien, car c'est un coût trivial qui n'est payé qu'une seule fois.
  • Si vous placez les importations dans une fonction, les appels vers cette fonction prendront plus de temps.

Donc, si vous vous souciez de l'efficacité, mettez les importations au sommet. Déplacez-les seulement dans une fonction si votre profilage montre que cela aiderait (vous avez profilé pour voir où améliorer les performances, non?)

Les meilleures raisons que j'ai vu pour effectuer des importations paresseuses sont:

  • Support de bibliothèque facultatif. Si votre code a plusieurs chemins qui utilisent des bibliothèques différentes, ne cassez pas si une bibliothèque facultative n'est pas installée.
  • Dans le __init__.py d'un plugin, qui peut être importé mais pas réellement utilisé. Les exemples sont les plugins Bazaar, qui utilisent le bzrlib de chargement bzrlib de bzrlib .

Voici un exemple où toutes les importations sont au sommet (c'est la seule fois que j'ai besoin de faire cela). Je veux être en mesure de terminer un sous-processus sur Un * x et Windows.

import os
# ...
try:
    kill = os.kill  # will raise AttributeError on Windows
    from signal import SIGTERM
    def terminate(process):
        kill(process.pid, SIGTERM)
except (AttributeError, ImportError):
    try:
        from win32api import TerminateProcess  # use win32api if available
        def terminate(process):
            TerminateProcess(int(process._handle), -1)
    except ImportError:
        def terminate(process):
            raise NotImplementedError  # define a dummy function

(En révision: ce que John Millikin a dit.)


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.


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.


Juste pour compléter la réponse de Moe et la question originale:

Quand nous devons faire face à des dépendances circulaires, nous pouvons faire quelques "tours". En supposant que nous travaillons avec les modules a.py et b.py qui contiennent x() et b y() , respectivement. Alors:

  1. Nous pouvons déplacer l'une des from imports en bas du module.
  2. Nous pouvons déplacer l'une des from imports la fonction ou de la méthode qui nécessite réellement l'importation (ce n'est pas toujours possible, car vous pouvez l'utiliser à plusieurs endroits).
  3. Nous pouvons changer l'un des deux à from imports pour être une importation qui ressemble à: import a

Donc, pour conclure. Si vous ne traitez pas de dépendances circulaires et que vous ne faites pas un tour pour les éviter, il est préférable de placer toutes vos importations au sommet pour les raisons déjà expliquées dans d'autres réponses à cette question. Et s'il vous plaît, quand vous faites ce "truc" inclure un commentaire, c'est toujours le bienvenu! :)


locals()["myfunction"]()

ou

globals()["myfunction"]()

locals renvoie un dictionnaire avec une table de symboles locale en cours. globals renvoie un dictionnaire avec une table de symboles globale.







python optimization coding-style