web-dev-qa-db-fra.com

Les instructions d'importation doivent-elles toujours figurer en haut du module?

PEP 08 déclare:

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 d'effectuer 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()
355
Adam J. Forster

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 profilez pour voir où il est préférable d’améliorer les performances, non?)


Les meilleures raisons que j'ai déjà vues d'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 plugin, qui peut être importé mais pas réellement utilisé. Des exemples sont les plugins Bazaar, qui utilisent le framework de chargement différé de bzrlib.
257
John Millikin

Placer l'instruction d'importation à l'intérieur d'une fonction peut éviter des dépendances circulaires. Par exemple, si vous avez 2 modules, X.py et Y.py, et qu'ils doivent s'importer l'un l'autre, cela créera une dépendance circulaire lorsque vous importerez l'un des modules, ce qui provoquera 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. Lire ici pour plus - effbot.org/zone/import-confusion.htm

73
Moe

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 je reçois est la capacité 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 à faire en sorte que les importations du nouveau module soient complètes et minimales. Un refactoring IDE pourrait rendre ceci 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, et 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 m'aider à comprendre un système dans la pratique.

Je mets généralement l'importation de sys à l'intérieur du contrôle if __name__=='__main__', puis passe des arguments (comme sys.argv[1:]) à une fonction main(). Cela me permet d’utiliser main dans un contexte où sys n’a pas été importé.

55
thousandlegs

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

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

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

Deuxièmement, vous pourriez avoir besoin d'importer de manière conditionnelle 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 importer des importations dans d'autres parties du code.

36

La première variante est en effet plus efficace que la seconde lorsque la fonction est appelée zéro ou une fois. Avec les deuxièmes invocations et les suivantes, cependant, l’approche "importer chaque appel" est en réalité moins efficace. Voir ce lien pour une technique de chargement paresseux combinant le meilleur des deux approches en effectuant une "importation paresseuse".

Mais il y a des raisons autres que l'efficacité pour lesquelles vous pourriez préférer l'une plutôt que l'autre. Une approche est beaucoup plus claire pour quelqu'un qui lit le code quant aux dépendances dont ce module dispose. Ils ont également des caractéristiques de défaillance très différentes: le premier échouera au moment du chargement s'il n'y a pas de module "datetime", tandis que le second échouera tant que la méthode n'aura pas été appelée.

Ajouté Remarque: Dans IronPython, les importations peuvent être un peu plus chères que dans CPython car le code est en cours de compilation lors de son importation.

14
Curt Hagenlocher

Je ne voudrais pas m'inquiéter de l'efficacité de charger le module à l'avance trop. 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 déboguer ceci 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 tu n'as quasiment pas le choix.

Je ne voudrais pas m'inquiéter de l'efficacité de charger le module à l'avance trop. 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.

8
Jason Baker

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

Normalement, je ne m'inquiète pas de l'efficacité du chargement des modules, car c'est (a) 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 de manière dynamique avec la fonction __import__ et sûr pour attraper ImportError exceptions, et les gérer de manière raisonnable.

8
Dan Lenski

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 vous en ayez besoin. Notez que le fait d'importer 'uniquement lorsque appelé' signifie également de le faire 'chaque fois qu'il est appelé', de sorte que chaque appel après le premier entraîne toujours la surcharge supplémentaire liée à l'importation.

Le cas 2 permet d’économiser du temps d’exécution et de la latence en important au préalable datetime afin que not_often_called () revienne plus rapidement quand il est appelé , et frais généraux d'une importation à chaque appel.

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

Personnellement, je suis généralement le PEP, sauf pour des tests unitaires et des choses telles que je ne veux pas toujours être chargé, car je sais qu'ils ne seront pas utilisé sauf pour le code de test.

6
pjz

Voici un exemple où toutes les importations sont tout en haut (c'est la seule fois où j'ai eu besoin de le faire). Je veux pouvoir terminer un sous-processus sous 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 revue: quoi John Millikin dit.)

6
giltay

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 fait votre travail de profilage et trouvé qu'il s'agit d'un changement suffisamment utile et vous avez besoin de la vitesse supplémentaire, 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
6
Drew Stephens

En plus des excellentes réponses déjà données, il convient de noter que le placement 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 où SparkContext est garanti.

4
Paul

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, la seule chose à faire est de lier 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 l'instruction d'importation à la portée du module.

4
Jeremy Brown

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

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

  1. Nous pouvons déplacer l’un des from imports au bas du module.
  2. Nous pouvons déplacer l'un des from imports à l'intérieur de 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 qu'il s'agisse d'une importation ressemblant à: import a

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, il est toujours le bienvenu! :)

4
Caumons

J'ai été surpris de ne pas voir les chiffres des coûts réels des contrôles de chargement répétés déjà postés, bien qu'il existe de nombreuses bonnes explications de 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 dans une ou plusieurs fonctions, alors vous ne prenez que le résultat du chargement si et lorsque l’une de ces fonctions est d'abord appelé. Comme beaucoup l'ont souligné, 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 hit répété bien que beaucoup plus petit (pour vérifier que a été chargé; pas pour le recharger réellement). 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#478996 ).

Voici les résultats d'un test simple qui importe quelques éléments de l'intérieur d'une fonction. Les temps signalés (dans Python2.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
3
TextGeek

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 python packages et modules 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 à 2 secondes environ. 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.

3
V.K.

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é correspond à ce qui est transmis à d’autres cœurs, par exemple. comme dans le cas d'ipyparallel.

2
K.-Michael Aye

Il peut y avoir un gain de performance en important des variables/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 tant que 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 sur 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

le vrai est 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

1
HarisankarK

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

Importations facultatives

J'analyse des 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 pouvais effectuer un redémarrage sans avoir à attendre l'importation ou à devoir reprendre le reste des cellules en cas d'échec.

0
Cedar