Comment changer une variable de module d'un autre module?

107

Supposons que j'ai un package nommé bar, et qu'il contient bar.py:

a = None

def foobar():
    print a

et __init__.py:

from bar import a, foobar

Ensuite, j'exécute ce script:

import bar

print bar.a
bar.a = 1
print bar.a
bar.foobar()

Voici ce que j'attends:

None
1
1

Voici ce que je reçois:

None
1
None

Quelqu'un peut-il expliquer mon idée fausse?

shino
la source

Réponses:

103

Vous utilisez from bar import a. adevient un symbole dans la portée globale du module d'importation (ou quelle que soit la portée dans laquelle l'instruction d'importation se produit).

Lorsque vous attribuez une nouvelle valeur à a, vous modifiez simplement la valeur qui apointe également, pas la valeur réelle. Essayez d'importer bar.pydirectement avec import barin __init__.pyet effectuez votre expérience là-bas en définissant bar.a = 1. De cette façon, vous modifierez bar.__dict__['a']réellement la valeur «réelle» de adans ce contexte.

C'est un peu compliqué avec trois couches mais bar.a = 1change la valeur de adans le module appelé barqui est en fait dérivé __init__.py. Cela ne change pas la valeur de ce aqui foobarvoit car il foobarvit dans le fichier réel bar.py. Vous pouvez définir bar.bar.asi vous souhaitez changer cela.

C'est l'un des dangers de l'utilisation de la from foo import barforme de l' importinstruction: elle se divise baren deux symboles, l'un visible globalement de l'intérieur fooduquel commence par pointer vers la valeur d'origine et un autre symbole visible dans la portée où l' importinstruction est exécutée. La modification d'un point de repère d'un symbole ne modifie pas non plus la valeur sur laquelle il pointe.

Ce genre de truc est un tueur lorsque vous essayez reloadun module de l'interpréteur interactif.

aaronasterling
la source
Merci! Votre réponse m'a probablement évité plusieurs heures de recherche du coupable.
jnns le
Il semble que même l' bar.bar.aapproche n'aidera pas beaucoup dans la plupart des cas d'utilisation. C'est probablement une idée faible de mélanger les affectations de compilation ou de module et d'exécution, car vous finirez par vous confondre (ou par d'autres qui utilisent votre code). Surtout dans les langages qui se comportent de manière quelque peu incohérente à ce sujet, comme le fait sans doute python.
matanster le
26

Une source de difficulté à cette question est que vous avez un programme nommé bar/bar.py: les import barimportations soit bar/__init__.pyou bar/bar.py, selon l'endroit où il est fait, ce qui le rend un peu lourd à suivre ce qui aest bar.a.

Voici comment cela fonctionne:

La clé pour comprendre ce qui se passe est de réaliser que dans votre __init__.py,

from bar import a

en effet fait quelque chose comme

a = bar.a
# … where bar = bar/bar.py (as if bar were imported locally from __init__.py)

et définit une nouvelle variable ( bar/__init__.py:asi vous le souhaitez). Ainsi, votre from bar import ain __init__.pylie le nom bar/__init__.py:aà l' bar.py:aobjet d' origine ( None). C'est pourquoi vous pouvez le faire from bar import a as a2dans __init__.py: dans ce cas, il est clair que vous avez les deux bar/bar.py:aet un nom de variable distinctbar/__init__.py:a2 (dans votre cas, les noms des deux variables se trouvent être les deux a, mais ils vivent toujours dans des espaces de noms différents: dans __init__.py, ils sont bar.aet a).

Maintenant, quand tu fais

import bar

print bar.a

vous accédez à la variable bar/__init__.py:a(depuis import barimporte votre bar/__init__.py). C'est la variable que vous modifiez (à 1). Vous ne touchez pas au contenu de la variable bar/bar.py:a. Alors quand tu fais par la suite

bar.foobar()

vous appelez bar/bar.py:foobar(), qui accède à la variable à apartir de bar/bar.py, qui est toujours None(quand foobar()est défini, il lie les noms de variable une fois pour toutes, donc le ain bar.pyn'est bar.py:apas une autre avariable définie dans un autre module - car il peut y avoir de nombreuses avariables dans tous les modules importés ). D'où la dernière Nonesortie.

Conclusion: il est préférable d'éviter toute ambiguïté import bar, par n'avoir aucun module (depuis répertoire marques un paquet déjà, que vous pouvez également importer avec ).bar/bar.pybar.__init__.pybar/import bar

Eric O Lebigot
la source
12

En d'autres termes: il s'avère que cette idée fausse est très facile à faire. Il est défini sournoisement dans la référence du langage Python: l'utilisation de l' objet au lieu du symbole . Je suggérerais que la référence du langage Python rende cela plus clair et moins épars.

Le fromformulaire ne lie pas le nom du module: il parcourt la liste des identifiants, recherche chacun d'eux dans le module trouvé à l'étape (1), et lie le nom dans l'espace de noms local à l' objet ainsi trouvé.

TOUTEFOIS:

Lorsque vous importez, vous importez la valeur actuelle du symbole importé et l'ajoutez à votre espace de noms tel que défini. Vous n'importez pas de référence, vous importez effectivement une valeur.

Ainsi, pour obtenir la valeur mise à jour de i, vous devez importer une variable qui contient une référence à ce symbole.

En d'autres termes, l'importation n'est PAS comme une déclaration importen JAVA, une externaldéclaration en C / C ++ ou même une useclause en PERL.

Au contraire, l'instruction suivante en Python:

from some_other_module import a as x

ressemble plus au code suivant dans K&R C:

extern int a; /* import from the EXTERN file */

int x = a;

(mise en garde: dans le cas Python, "a" et "x" sont essentiellement une référence à la valeur réelle: vous ne copiez pas l'INT, vous copiez l'adresse de référence)

Mark Gerolimatos
la source
En fait, je trouve que la méthode Python est importbeaucoup plus propre que celle de Java, car les espaces de noms / portées doivent toujours être correctement séparés et ne jamais interférer les uns avec les autres de manière inattendue. Comme ici: Modifier une liaison d'un objet à un nom dans un espace de noms (lire: associer quelque chose à la propriété globale d'un module) n'affecte jamais les autres espaces de noms (lire: la référence qui a été importée) en Python. Mais il le fait en Java, etc. En Python, vous avez seulement besoin de comprendre ce qui est importé, tandis qu'en Java, vous devez également comprendre l'autre module au cas où il modifierait cette valeur importée plus tard.
Tino
2
Je dois être en désaccord assez énergique. L'importation / l'inclusion / l'utilisation a une longue histoire d'utilisation du formulaire de référence et non du formulaire de valeur dans toutes les autres langues.
Mark Gerolimatos
4
Bon sang, j'ai frappé retour… il y a une attente d'importation par référence (selon @OP). En fait, un nouveau programmeur Python, aussi expérimenté soit-il, doit se faire dire cela de la manière «regarder». Cela ne devrait jamais arriver: sur la base d'un usage courant, Python a pris le mauvais chemin. Créez une "valeur d'importation" si nécessaire, mais ne confondez pas les symboles avec les valeurs au moment de l'importation.
Mark Gerolimatos