Question

PEP 08 indique:

  

Les importations sont toujours placées en haut du fichier, juste après les commentaires de module et les docstrings, et avant les globales et les constantes de module.

Cependant, si la classe / méthode / fonction que je suis en train d'importer n'est utilisée que dans de rares cas, il est sûrement plus efficace de faire l'importation lorsque 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()
Était-ce utile?

La solution

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

  • Placer les importations en haut du module est acceptable, car il s'agit d'un coût trivial qui n'est payé qu'une seule fois.
  • En plaçant les importations dans une fonction, les appels à cette fonction prendront plus de temps.

Donc, si vous vous souciez de l'efficacité, placez les importations en haut. Ne les déplacez dans une fonction que si votre profilage indique que cela aiderait (vous avez développé le profil pour voir où il serait préférable d'améliorer les performances, non?)

Les meilleures raisons que je connaisse pour effectuer des importations paresseuses sont les suivantes:

  • Support de bibliothèque optionnel. Si votre code comporte plusieurs chemins qui utilisent des bibliothèques différentes, n'interrompez pas l'opération si une bibliothèque facultative n'est pas installée.
  • Dans le __ init __. py d'un plug-in, qui peut être importé mais pas réellement utilisé. Des exemples sont les plugins Bazaar, qui utilisent le framework de chargement paresseux de bzrlib .

Autres conseils

Le fait de placer l'instruction d'importation dans une fonction peut empêcher les dépendances circulaires. Par exemple, si vous avez 2 modules, X.py et Y.py, et que les deux doivent s'importer l'un l'autre, cela entraînera une dépendance circulaire lorsque vous importez l'un des modules, ce qui provoque une boucle infinie. Si vous déplacez l'instruction d'importation dans l'un des modules, il ne tentera pas d'importer l'autre module tant que la fonction n'aura pas été appelée. Ce module sera déjà importé, donc pas de boucle infinie. Lisez ici pour plus d'informations - effbot.org/zone/import-confusion.htm

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

L’avantage que j’obtiens est la possibilité de refactoriser de manière plus fiable. Lorsque je déplace une fonction d'un module à un autre, je sais que cette fonction continuera de fonctionner avec tous ses tests existants. Si mes importations figurent en haut du module, lorsque je déplace une fonction, je finis par passer beaucoup de temps à terminer et à minimiser les importations du nouveau module. Un IDE de refactoring pourrait rendre cela inutile.

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 besoins.

Il est également agréable de pouvoir visualiser toutes les dépendances de modules sans recourir à la recherche (par exemple, grep). Cependant, les dépendances de modules m'intéressent généralement parce que j'installe, refacture ou déplace un système complet comprenant plusieurs fichiers, pas seulement un seul module. Dans ce cas, je vais quand même effectuer une recherche globale pour m'assurer que je dispose des dépendances au niveau du système. Je n’ai donc pas trouvé d’importations globales pour mieux comprendre un système dans la pratique.

Je place généralement l'importation de sys dans le si __name __ == '__ main __' vérifie puis passe des arguments (comme sys.argv [1:] ) en une fonction main () . Cela me permet d’utiliser main dans un contexte où sys n’a pas été importé.

Cela serait utile par souci de clarté et judicieux, mais ce n’est pas toujours le cas. Vous trouverez ci-dessous quelques exemples de circonstances dans lesquelles les importations de modules pourraient vivre ailleurs.

Tout d'abord, vous pourriez avoir un module avec un test unitaire de la forme suivante:

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

Deuxièmement, vous devrez peut-être importer conditionnellement un module différent au moment de l'exécution.

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

Il existe probablement d'autres situations dans lesquelles vous pouvez placer des importations dans d'autres parties du code.

Curt avance un bon point: la deuxième version est plus claire et échouera au chargement plutôt que plus tard et de manière inattendue.

Normalement, l'efficacité du chargement des modules ne m'inquiète pas, car c'est (a) assez rapide et (b) surtout au démarrage.

Si vous devez charger des modules lourds à des moments inattendus, il est probablement plus judicieux de les charger de manière dynamique avec la fonction __ import __ et assurez-vous de saisir . ImportError et les gérer de manière raisonnable.

Je ne voudrais pas m'inquiéter de l'efficacité de charger le module trop à l'avance. La mémoire occupée par le module ne sera pas très grande (en supposant qu’elle soit suffisamment modulaire) et le coût de démarrage sera négligeable.

Dans la plupart des cas, vous souhaitez charger les modules en haut du fichier source. Pour quelqu'un qui lit votre code, il est beaucoup plus facile de dire quelle fonction ou quel objet provient de quel module.

Une bonne raison d'importer un module ailleurs dans le code est son utilisation dans une instruction de débogage.

Par exemple:

do_something_with_x(x)

Je pourrais résoudre ce problème avec:

from pprint import pprint
pprint(x)
do_something_with_x(x)

Bien entendu, l’autre raison d’importer des modules ailleurs dans le code est si vous devez les importer de manière dynamique. C’est parce que vous n’avez pratiquement pas le choix.

Je ne voudrais pas m'inquiéter de l'efficacité de charger le module trop à l'avance. La mémoire occupée par le module ne sera pas très grande (en supposant qu’elle soit suffisamment modulaire) et le coût de démarrage sera négligeable.

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 effectuant l'initialisation nécessaire) jusqu'à ce que cela soit nécessaire. Notez que faire l'importation «uniquement à l'appel» signifie également à le faire «à chaque appel», de sorte que chaque appel suivant le premier entraîne toujours la surcharge supplémentaire liée à l'importation.

Le cas 2 économise du temps d’exécution et de la latence en important au préalable datetime afin que not_often_called () revienne plus rapidement lorsqu’il est appelé , et en évitant de subir le surcoût d’une importation à chaque appel.

Outre l'efficacité, il est plus facile de voir les dépendances de modules à l'avance si les instructions d'importation sont ... à l'avance. Les cacher dans le code peut rendre plus difficile la recherche des modules dont dépend quelque chose.

Personnellement, je suis généralement conforme au PEP, sauf pour les tests unitaires, de sorte que je ne veuille pas toujours être chargé, car je sais qu'ils ne seront pas utilisés, sauf pour le code de test.

Cela ressemble à de nombreuses autres optimisations: vous sacrifiez une partie de la lisibilité au profit de la vitesse. Comme John l'a mentionné, si vous avez défini votre travail de profilage et constaté qu'il s'agit d'un changement suffisamment utile et , vous avez besoin de plus de rapidité, alors foncez. 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

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

Associez ces informations à l'argument de lisibilité et je dirais qu'il est préférable que l'instruction d'importation soit à la portée du module.

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

Lorsque nous devons traiter des dépendances circulaires, nous pouvons faire des "astuces". En supposant que nous travaillons avec les modules a.py et b. py contenant x () et b y () , respectivement. Puis:

  1. Nous pouvons déplacer l'un des des importations au bas du module.
  2. Nous pouvons déplacer l'un des depuis les importations à l'intérieur de la fonction ou de la méthode nécessitant réellement l'importation (ce n'est pas toujours possible, car vous pouvez l'utiliser à plusieurs endroits).
  3. Nous pouvons modifier l'un des deux importation pour qu'il s'agisse d'une importation ressemblant à: importer un

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

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

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

Je n’aspire pas à fournir une réponse complète, car d’autres l’ont déjà très bien fait. Je veux juste mentionner un cas d'utilisation lorsque je trouve particulièrement utile d'importer des modules dans des fonctions. Mon application utilise des packages et des modules Python stockés à certains emplacements en tant que plugins. Lors du démarrage de l'application, l'application parcourt tous les modules de l'emplacement et les importe, puis examine l'intérieur des modules et, le cas échéant, trouve des points de montage pour les plug-ins (dans mon cas, il s'agit d'une sous-classe d'une classe de base spécifique. ID) il les enregistre. Le nombre de plugins est grand (maintenant des dizaines, mais peut-être des centaines dans le futur) et chacun d'entre eux est utilisé assez rarement. Avoir des importations de bibliothèques tierces au sommet de mes modules de plug-in était un peu pénalisant lors du démarrage de l'application. Certaines bibliothèques tierces sont particulièrement lourdes à importer (par exemple, l’importation de complot tente même de se connecter à Internet et de télécharger quelque chose qui demandait environ une seconde au démarrage). En optimisant les importations (en les appelant uniquement dans les fonctions où elles sont utilisées) dans les plugins, j'ai réussi à réduire le démarrage de 10 secondes à environ 2 secondes. C'est une grande différence pour mes utilisateurs.

Donc, ma réponse est non, ne placez pas toujours les importations en haut de vos modules.

J'ai été surpris de ne pas voir déjà les chiffres des coûts réels des contrôles de chargement répétés déjà publiés, bien qu'il existe de nombreuses bonnes explications sur ce à quoi s'attendre.

Si vous importez en haut, vous prenez la charge à tout prix. C'est assez petit, mais généralement en millisecondes, pas en nanosecondes.

Si vous importez au sein d'une ou plusieurs fonctions, vous ne récupérerez que le chargement si et lorsque une de ces fonctions est d'abord appelée. Comme beaucoup l'ont fait remarquer, si cela ne se produit pas du tout, vous gagnez du temps de chargement. Mais si la / les fonction (s) sont beaucoup appelées, vous prenez un résultat répété bien que beaucoup plus petit (pour vérifier si elle a été chargée ; pas pour le rechargement effectif). D'autre part, comme l'a souligné @aaronasterling, vous enregistrez également un peu, car l'importation dans une fonction permet à la fonction d'utiliser des recherches de variable locale légèrement plus rapides pour identifier le nom ultérieurement (http://stackoverflow.com/questions/477096/python-import-coding-style/4789963#4789963 ).

Voici les résultats d'un test simple qui importe quelques éléments de l'intérieur d'une fonction. Les temps signalés (en Python 2.7.14 sur un Intel Core i7 à 2,3 GHz) sont indiqués ci-dessous (le deuxième appel prenant plus que les appels ultérieurs semble cohérent, bien que je ne sache pas pourquoi).

 0 foo:   14429.0924 µs
 1 foo:      63.8962 µs
 2 foo:      10.0136 µs
 3 foo:       7.1526 µs
 4 foo:       7.8678 µs
 0 bar:       9.0599 µs
 1 bar:       6.9141 µs
 2 bar:       7.1526 µs
 3 bar:       7.8678 µs
 4 bar:       7.1526 µs

Le code:

from __future__ import print_function
from time import time

def foo():
    import collections
    import re
    import string
    import math
    import subprocess
    return

def bar():
    import collections
    import re
    import string
    import math
    import subprocess
    return

t0 = time()
for i in xrange(5):
    foo()
    t1 = time()
    print("    %2d foo: %12.4f \xC2\xB5s" % (i, (t1-t0)*1E6))
    t0 = t1
for i in xrange(5):
    bar()
    t1 = time()
    print("    %2d bar: %12.4f \xC2\xB5s" % (i, (t1-t0)*1E6))
    t0 = t1

Il est intéressant de noter qu’à ce jour, pas une seule réponse ne mentionnait le traitement parallèle, où il serait peut-être NÉCESSAIRE que les importations figurent dans la fonction, lorsque le code de la fonction sérialisée correspond à ce qui est transmis à d’autres cœurs, par exemple. comme dans le cas d'ipyparallel.

Il peut y avoir un gain de performance en important des variables / une portée locale à l’intérieur d’une fonction. Cela dépend de l'utilisation de la chose importée dans la fonction. Si vous bouclez plusieurs fois et que vous accédez à un objet global de module, l'importer en local peut vous aider.

test.py

X=10
Y=11
Z=12
def add(i):
  i = i + 10

runlocal.py

from test import add, X, Y, Z

    def callme():
      x=X
      y=Y
      z=Z
      ladd=add 
      for i  in range(100000000):
        ladd(i)
        x+y+z

    callme()

run.py

from test import add, X, Y, Z

def callme():
  for i in range(100000000):
    add(i)
    X+Y+Z

callme()

Un temps sous Linux montre un petit gain

/usr/bin/time -f "\t%E real,\t%U user,\t%S sys" python run.py 
    0:17.80 real,   17.77 user, 0.01 sys
/tmp/test$ /usr/bin/time -f "\t%E real,\t%U user,\t%S sys" python runlocal.py 
    0:14.23 real,   14.22 user, 0.01 sys

real est une horloge murale. l'utilisateur est le temps dans le programme. sys est l'heure des appels système.

https://docs.python.org/3.5 /reference/executionmodel.html#resolution-of-names

Je voudrais mentionner un de mes cas d'utilisation, très similaire à ceux mentionnés par @John Millikin et @ V.K. :

Importations facultatives

Je fais l’analyse de données avec Jupyter Notebook et j’utilise le même cahier IPython comme modèle pour toutes les analyses. Dans certains cas, j'ai besoin d'importer Tensorflow pour pouvoir exécuter rapidement des modèles, mais parfois, je travaille dans des endroits où tensorflow n'est pas configuré / il est lent à importer. Dans ces cas, j'encapsule mes opérations dépendantes de Tensorflow dans une fonction d'assistance, importe le tensorflow à l'intérieur de cette fonction et le lie à un bouton.

De cette façon, je pourrais faire "redémarrer et exécuter tout". sans attendre l'importation ni reprendre le reste des cellules en cas d'échec.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top