web-dev-qa-db-fra.com

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

Supposons que j'ai un package nommé bar et qu'il contienne 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 j'obtiens:

None
1
None

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

83
shino

Vous utilisez from bar import a. a devient un symbole dans la portée globale du module d'importation (ou dans la portée de la déclaration d'importation).

Lorsque vous affectez une nouvelle valeur à a, vous modifiez simplement la valeur a qui pointe également, pas la valeur réelle. Essayez d'importer bar.py directement avec import bar dans __init__.py et conduisez votre expérience là-bas en définissant bar.a = 1. De cette façon, vous allez réellement modifier bar.__dict__['a'] qui est la valeur "réelle" de a dans ce contexte.

C'est un peu compliqué avec trois couches mais bar.a = 1 change la valeur de a dans le module appelé bar qui est en fait dérivé de __init__.py. Il ne modifie pas la valeur de a que foobar voit car foobar réside dans le fichier réel bar.py. Vous pouvez définir bar.bar.a si vous vouliez changer cela.

C'est l'un des dangers de l'utilisation du from foo import bar forme de l'instruction import: elle divise bar en deux symboles, l'un visible globalement de l'intérieur foo qui commence par pointer vers la valeur d'origine et un autre symbole visible dans l'étendue où l'instruction import est exécutée. La modification de l'endroit où pointe un symbole ne modifie pas non plus la valeur qu'il indiquait.

Ce genre de choses est un tueur lorsque vous essayez de reload un module à partir de l'interpréteur interactif.

78
aaronasterling

Une source de difficulté avec cette question est que vous avez un programme nommé bar/bar.py: import bar Importe soit bar/__init__.py Soit bar/bar.py, Selon l'endroit où il est fait, ce qui rend un peu compliqué le suivi de quel a est 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

fait en fait quelque chose comme

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

et définit une nouvelle variable (bar/__init__.py:a, si vous le souhaitez). Ainsi, votre from bar import a Dans __init__.py Lie le nom bar/__init__.py:a À l'objet bar.py:a D'origine (None). C'est pourquoi vous pouvez faire from bar import a as a2 Dans __init__.py: Dans ce cas, il est clair que vous avez à la fois bar/bar.py:a Et un distinct nom de variable bar/__init__.py:a2 (Dans votre cas, les noms des deux variables se trouvent être a, mais ils vivent toujours dans des espaces de noms différents: dans __init__.py, Ils sont bar.a et a).

Maintenant, quand vous le faites

import bar

print bar.a

vous accédez à la variable bar/__init__.py:a (puisque import bar importe votre bar/__init__.py). Il s'agit de la variable que vous modifiez (à 1). Vous ne touchez pas le contenu de la variable bar/bar.py:a. Donc, quand vous faites par la suite

bar.foobar()

vous appelez bar/bar.py:foobar(), qui accède à la variable a à partir de bar/bar.py, qui est toujours None (lorsque foobar() est définie, elle lie les noms de variables une fois pour toutes, donc la a dans bar.py est bar.py:a, pas toute autre variable a définie dans un autre module - car il peut y en avoir plusieurs a variables dans tous les modules importés). D'où la dernière sortie de None.

18
Eric O Lebigot

Autrement dit: il s'avère que cette idée fausse est très facile à faire. Il est défini sournoisement dans le Python: l'utilisation de objet au lieu de symbole =. Je suggère que la référence du langage Python rend cela plus clair et moins clairsemé ..

Le formulaire from 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 de l'espace de noms local au objet ainsi trouvé.

CEPENDANT:

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

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

En d'autres termes, l'importation n'est PAS comme une déclaration import en Java, external en C/C++ ou même une clause use en Perl.

Au contraire, l'instruction suivante en Python:

from some_other_module import a as x

est plus comme le code suivant dans K&R C:

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

int x = a;

(mise en garde: dans le cas Python cas, "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)

10