web-dev-qa-db-fra.com

Instructions d'importation locales dans Python

Je pense que mettre la déclaration d'importation aussi près du fragment qui l'utilise aide à la lisibilité en rendant ses dépendances plus claires. Est-ce que Python cache cela? Dois-je m'en soucier? Est-ce une mauvaise idée?

def Process():
    import StringIO
    file_handle=StringIO.StringIO('hello world')
    #do more stuff

for i in xrange(10): Process()

Un peu plus de justification: c'est pour les méthodes qui utilisent des bits arcaniques de la bibliothèque, mais quand je refactorise la méthode dans un autre fichier, je ne réalise pas que j'ai manqué la dépendance externe jusqu'à ce que j'obtienne une erreur d'exécution.

45
Dustin Getz

Les autres réponses témoignent d'une légère confusion quant au fonctionnement réel de import.

Cette déclaration:

import foo

est à peu près équivalent à cette déclaration:

foo = __import__('foo', globals(), locals(), [], -1)

Autrement dit, il crée une variable dans la portée actuelle avec le même nom que le module demandé, et lui affecte le résultat de l'appel de __import__() avec ce nom de module et une charge d'arguments par défaut.

La fonction __import__() gère la conversion conceptuelle d'une chaîne ('foo') En objet module. Les modules sont mis en cache dans sys.modules, Et c'est le premier endroit où __import__() ressemble - si sys.modules a une entrée pour 'foo', C'est ce que __import__('foo') reviendra, quoi que ce soit. Il ne se soucie vraiment pas du type. Vous pouvez le voir vous-même en action; essayez d'exécuter le code suivant:

import sys
sys.modules['boop'] = (1, 2, 3)
import boop
print boop

Laissant de côté les préoccupations stylistiques pour le moment, avoir une instruction d'importation dans une fonction fonctionne comme vous le souhaitez. Si le module n'a jamais été importé auparavant, il est importé et mis en cache dans sys.modules. Il affecte ensuite le module à la variable locale portant ce nom. Il ne pas non pas modifie tout état au niveau du module. Il fait éventuellement modifier un état global (en ajoutant une nouvelle entrée à sys.modules).

Cela dit, je n'utilise presque jamais import dans une fonction. Si l'importation du module crée un ralentissement notable dans votre programme - comme s'il effectue un long calcul dans son initialisation statique, ou c'est simplement un module massif - et que votre programme a rarement besoin du module pour quoi que ce soit, il est tout à fait correct d'avoir l'importation uniquement à l'intérieur les fonctions dans lesquelles il est utilisé. (Si c'était désagréable, Guido sauterait dans sa machine à remonter le temps et changerait Python pour nous empêcher de le faire.) Mais en règle générale, moi et la communauté générale Python mettions tout notre importez les instructions en haut du module dans la portée du module.

72
Larry Hastings

Veuillez voir PEP 8 :

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

Veuillez noter qu'il s'agit d'un choix purement stylistique car Python traitera toutes les instructions import de la même manière, peu importe où elles sont déclarées dans le fichier source. Néanmoins, je vous recommande de suivre pratique courante car cela rendra votre code plus lisible pour les autres.

13
Andrew Hare

Mis à part le style, il est vrai qu'un module importé ne sera importé qu'une seule fois (sauf si reload est appelé sur ledit module). Cependant, chaque appel à import Foo devra implicitement vérifier si ce module est déjà chargé (en vérifiant sys.modules ).

Considérez également le "démontage" de deux fonctions par ailleurs égales où l'une essaie d'importer un module et l'autre pas:

>>> def Foo():
...     import random
...     return random.randint(1,100)
... 
>>> dis.dis(Foo)
  2           0 LOAD_CONST               1 (-1)
              3 LOAD_CONST               0 (None)
              6 IMPORT_NAME              0 (random)
              9 STORE_FAST               0 (random)

  3          12 LOAD_FAST                0 (random)
             15 LOAD_ATTR                1 (randint)
             18 LOAD_CONST               2 (1)
             21 LOAD_CONST               3 (100)
             24 CALL_FUNCTION            2
             27 RETURN_VALUE        
>>> def Bar():
...     return random.randint(1,100)
... 
>>> dis.dis(Bar)
  2           0 LOAD_GLOBAL              0 (random)
              3 LOAD_ATTR                1 (randint)
              6 LOAD_CONST               1 (1)
              9 LOAD_CONST               2 (100)
             12 CALL_FUNCTION            2
             15 RETURN_VALUE        

Je ne sais pas combien de plus le bytecode est traduit pour la machine virtuelle, mais s'il s'agissait d'une boucle interne importante de votre programme, vous voudriez certainement mettre un peu de poids sur l'approche Bar sur Foo approche.

Un test timeit rapide et sale montre une amélioration de vitesse modeste lors de l'utilisation de Bar:

$ python -m timeit -s "from a import Foo,Bar" -n 200000 "Foo()"
200000 loops, best of 3: 10.3 usec per loop
$ python -m timeit -s "from a import Foo,Bar" -n 200000 "Bar()"
200000 loops, best of 3: 6.45 usec per loop
10
Mark Rushakoff

Je l'ai fait et j'aurais souhaité ne pas l'avoir fait. Normalement, si j'écris une fonction et que cette fonction doit utiliser StringIO, je peux regarder en haut du module, voir s'il est importé, puis l'ajouter si ce n'est pas le cas.

Supposons que je ne fasse pas cela; supposons que je l'ajoute localement dans ma fonction. Et supposons ensuite qu'à quelqu'un, je, ou quelqu'un d'autre, ajoute un tas d'autres fonctions qui utilisent StringIO. Cette personne va regarder en haut du module et ajouter import StringIO. Maintenant, votre fonction contient du code non seulement inattendu mais redondant.

En outre, cela viole ce que je pense être un principe assez important: ne modifiez pas directement l'état au niveau du module depuis l'intérieur d'une fonction.

Modifier:

En fait, il s'avère que tout ce qui précède est un non-sens.

L'importation d'un module ne modifie pas l'état du module (il initialise le module importé, si rien d'autre ne l'a encore fait, mais ce n'est pas du tout le même chose). L'importation d'un module que vous avez déjà importé ailleurs ne vous coûte rien sauf une recherche vers sys.modules et en créant une variable dans la portée locale.

Sachant cela, je me sens un peu stupide de fixer tous les endroits de mon code où je l'ai fixé, mais c'est ma croix à porter.

8
Robert Rossney

Lorsque l'interpréteur Python frappe une instruction d'importation, il commence à lire toutes les définitions de fonction dans le fichier en cours d'importation. Cela explique pourquoi parfois, les importations peuvent prendre un certain temps.

L'idée derrière toute l'importation au début IS une convention stylistique comme le souligne Andrew Hare. Cependant, vous devez garder à l'esprit qu'en faisant cela, vous faites implicitement vérifier à l'interpréteur si ce fichier a déjà été importé après la première importation. Cela devient également un problème lorsque votre fichier de code devient volumineux et que vous souhaitez "mettre à niveau" votre code pour supprimer ou remplacer certaines dépendances. Cela vous obligera à rechercher tout votre code fichier pour trouver tous les endroits où vous avez importé ce module.

Je suggère de suivre la convention et de conserver les importations en haut de votre fichier de code. Si vous voulez vraiment garder une trace des dépendances pour les fonctions, je vous suggère de les ajouter dans le docstring pour cette fonction.

3
inspectorG4dget