web-dev-qa-db-fra.com

Style de codage d'importation Python

J'ai découvert un nouveau motif. Ce modèle est-il bien connu ou quelle est son opinion?

En gros, j’ai du mal à parcourir les fichiers sources pour déterminer les importations de modules disponibles, etc., alors maintenant, au lieu de

import foo
from bar.baz import quux

def myFunction():
    foo.this.that(quux)

Je déplace toutes mes importations dans la fonction où elles sont réellement utilisées., Comme ceci:

def myFunction():
    import foo
    from bar.baz import quux

    foo.this.that(quux)

Cela fait quelques choses. Premièrement, je pollue rarement par inadvertance mes modules avec le contenu d’autres modules. Je pourrais définir la variable __all__ pour le module, mais je devrais alors la mettre à jour à mesure que le module évolue, ce qui n'aide pas la pollution de l'espace de noms pour le code qui réside réellement dans le module.

Deuxièmement, je finis rarement avec une litanie d'importations au sommet de mes modules, dont la moitié ou plus dont je n'ai plus besoin parce que je l'ai refactorisé. Enfin, je trouve ce modèle BEAUCOUP plus facile à lire, car chaque nom référencé se trouve exactement dans le corps de la fonction.

59
TokenMacGuy

La réponse (précédemment) la plus votée à cette question est joliment formatée mais absolument fausse en matière de performances. Laissez-moi démontrer

Performance

Top Import

import random

def f():
    L = []
    for i in xrange(1000):
        L.append(random.random())


for i in xrange(1000):
    f()

$ time python import.py

real        0m0.721s
user        0m0.412s
sys         0m0.020s

Importer dans un corps de fonction

def f():
    import random
    L = []
    for i in xrange(1000):
        L.append(random.random())

for i in xrange(1000):
    f()

$ time python import2.py

real        0m0.661s
user        0m0.404s
sys         0m0.008s

Comme vous pouvez le constater, il peut être plus efficace d’importer le module dans la fonction. La raison en est simple. Il déplace la référence d'une référence globale vers une référence locale. Cela signifie que, pour CPython au moins, le compilateur émettra des instructions LOAD_FAST au lieu de LOAD_GLOBAL. Celles-ci sont, comme leur nom l'indique, plus rapides. L'autre répondant a gonflé artificiellement le coup de performance de regarder dans sys.modules de important à chaque itération de la boucle

En règle générale, il est préférable d’importer par le haut, mais les performances sont non la raison pour laquelle vous accédez au module à de nombreuses reprises. Les raisons sont que l'on peut garder une trace de ce dont dépend un module plus facilement et que cela est cohérent avec la plupart du reste de l'univers Python.

102
aaronasterling

Cela a quelques inconvénients.

Essai

Si vous souhaitez tester votre module par le biais d'une modification d'exécution, cela risque de compliquer les choses. Au lieu de faire

import mymodule
mymodule.othermodule = module_stub

Vous devrez faire

import othermodule
othermodule.foo = foo_stub

Cela signifie que vous devrez patcher l'autre module globalement, au lieu de changer simplement ce que la référence dans mymodule indique.

Suivi de dépendance

Cela rend évident le type de module dont dépend votre module. Cela est particulièrement irritant si vous utilisez plusieurs bibliothèques tierces ou si vous réorganisez du code.

Je devais conserver un code hérité qui utilisait des importations en ligne partout, ce qui le rendait extrêmement difficile à refactoriser ou à reconditionner.

Notes sur les performances

En raison de la manière dont python met les modules en cache, il n’ya pas de problème de performances. En fait, étant donné que le module se trouve dans l’espace de noms local, l’importation de modules dans une fonction présente un léger avantage en termes de performances.

Top Import

import random

def f():
    L = []
    for i in xrange(1000):
        L.append(random.random())

for i in xrange(10000):
    f()


$ time python test.py 

real   0m1.569s
user   0m1.560s
sys    0m0.010s

Importer dans un corps de fonction

def f():
    import random
    L = []
    for i in xrange(1000):
        L.append(random.random())

for i in xrange(10000):
    f()

$ time python test2.py

real    0m1.385s
user    0m1.380s
sys     0m0.000s
53
Ryan

Quelques problèmes avec cette approche:

  • Lors de l’ouverture du fichier, les modules dont il dépend ne sont pas immédiatement évidents.
  • Cela confondra les programmes qui doivent analyser des dépendances, telles que py2exe, py2app etc.
  • Qu'en est-il des modules que vous utilisez dans de nombreuses fonctions? Vous vous retrouverez avec beaucoup d'importations redondantes ou vous devrez en avoir quelques unes en haut du fichier et quelques fonctions internes.

Donc ... le moyen préféré est de placer toutes les importations en haut du fichier. J'ai constaté que si mes importations devenaient difficiles à suivre, cela signifiait généralement que j'avais trop de code pour qu'il faille mieux le scinder en deux fichiers ou plus.

Certaines situations où je ai ont trouvé des importations dans les fonctions sont utiles:

  • Pour gérer les dépendances circulaires (si vous ne pouvez vraiment pas les éviter)
  • Code spécifique à la plateforme

Aussi: mettre les importations à l'intérieur de chaque fonction est en réalité pas sensiblement plus lent qu'en haut du fichier. La première fois que chaque module est chargé, il est placé dans sys.modules, et chaque importation ultérieur ne coûte que le temps de rechercher le module, ce qui est assez rapide (il n'est pas rechargé).

19
dF.

Une autre chose utile à noter est que la syntaxe from module import * à l'intérieur d'une fonction a été supprimée dans Python 3.0.

Vous en trouverez une brève mention sous "Syntaxe supprimée" ici:

http://docs.python.org/3.0/whatsnew/3.0.html

10
Russell Bryant

Je suggérerais que vous essayiez d'éviter les importations from foo import bar. Je ne les utilise que dans des packages, où la scission en modules est un détail d'implémentation et il n'y en aura pas beaucoup de toute façon.

Dans tous les autres endroits où vous importez un package, utilisez simplement import foo, puis faites référence à celui-ci par le nom complet foo.bar. De cette façon, vous pouvez toujours savoir d'où provient un élément donné et ne pas avoir à gérer la liste des éléments importés (en réalité, ce sera toujours obsolète et les éléments importés ne seront plus utilisés). 

Si foo est un nom très long, vous pouvez le simplifier avec import foo as f puis écrire f.bar. Cela reste encore beaucoup plus pratique et explicite que de conserver toutes les importations from.

4
nikow

Les gens ont très bien expliqué pourquoi il fallait éviter les importations en ligne, mais pas de flux de travail alternatifs pour traiter les raisons pour lesquelles vous les voulez au départ.

J'ai du mal à parcourir les fichiers sources pour déterminer les importations de modules disponibles, etc.

Pour vérifier les importations non utilisées, j'utilise pylint . Il effectue une analyse statique (ish) du code Python, et l'une des (nombreuses) choses qu'il vérifie est les importations inutilisées. Par exemple, le script suivant ..

import urllib
import urllib2

urllib.urlopen("http://stackoverflow.com")

..would générer le message suivant:

example.py:2 [W0611] Unused import urllib2

En ce qui concerne la vérification des importations disponibles, je m'appuie généralement sur l'achèvement (assez simpliste) de TextMate - lorsque vous appuyez sur Échap, il complète le mot actuel avec les autres éléments du document. Si j'ai fait import urllib, urll[Esc] passera à urllib, sinon je saute au début du fichier et ajoute l'importation.

3
dbr

Vous voudrez peut-être jeter un coup d'œil à Import statement overhead dans le wiki python. En bref: si le module a déjà été chargé (regardez sys.modules), votre code fonctionnera plus lentement. Si votre module n'a pas encore été chargé et que foo ne sera chargé qu'en cas de besoin, ce qui peut être zéro fois, les performances globales seront meilleures.

2
RSabet

Du point de vue des performances, vous pouvez voir ceci: Les instructions d’importation Python doivent-elles toujours figurer en haut d’un module?

En général, je n'utilise que les importations locales pour rompre les cycles de dépendance.

2
sykora

Je crois que cette approche est recommandée dans certains cas/scénarios. Par exemple, dans Google App Engine, le chargement différé de gros modules est recommandé car cela minimisera le coût de réchauffement lié à l'instanciation de nouveaux Python ordinateurs virtuels/interpréteurs. Jetez un œil à une présentation de Google Engine décrivant cela. Cependant, gardez à l'esprit que ne signifie pas , vous devez charger tous vos modules paresseux.

2
fuentesjr