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?
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.
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
.
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)