Wie ändere ich eine Modulvariable von einem anderen Modul?

105

Angenommen, ich habe ein Paket mit dem Namen barund es enthält bar.py:

a = None

def foobar():
    print a

und __init__.py:

from bar import a, foobar

Dann führe ich dieses Skript aus:

import bar

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

Folgendes erwarte ich:

None
1
1

Folgendes bekomme ich:

None
1
None

Kann jemand mein Missverständnis erklären?

Shino
quelle

Antworten:

103

Sie verwenden from bar import a. awird zu einem Symbol im globalen Bereich des Importmoduls (oder in welchem ​​Bereich auch immer die Importanweisung auftritt).

Wenn Sie einen neuen Wert zuweisen a, ändern Sie nur, welche Werte ebenfalls aangezeigt werden, nicht den tatsächlichen Wert. Versuchen Sie, bar.pydirekt mit import barin zu importieren __init__.pyund führen Sie Ihr Experiment dort durch Einstellen durch bar.a = 1. Auf diese Weise ändern Sie tatsächlich, bar.__dict__['a']welcher Wert ain diesem Zusammenhang „echt“ ist .

Es ist ein wenig verworren mit drei Ebenen, bar.a = 1ändert aber den Wert des aaufgerufenen Moduls bar, von dem tatsächlich abgeleitet wird __init__.py. Es ändert nichts an dem Wert a, den das foobarsieht, weil es foobarin der eigentlichen Datei lebt bar.py. Sie können festlegen, bar.bar.aob Sie dies ändern möchten.

Dies ist eine der Gefahren bei der Verwendung der from foo import barForm der importAnweisung: Sie teilt sich barin zwei Symbole auf, von denen eines global sichtbar ist foound zunächst auf den ursprünglichen Wert zeigt, und ein anderes Symbol, das in dem Bereich sichtbar ist, in dem die importAnweisung ausgeführt wird. Wenn Sie eine Stelle ändern, auf die ein Symbol zeigt, ändert sich auch nicht der Wert, auf den es zeigt.

Diese Art von Dingen ist ein Killer, wenn Sie versuchen, reloadein Modul vom interaktiven Interpreter zu verwenden.

aaronasterling
quelle
Vielen Dank! Ihre Antwort hat mir wahrscheinlich nur einige Stunden erspart, nach dem Täter zu suchen.
jnns
Es sieht so aus, als würde selbst der bar.bar.aAnsatz in den meisten Anwendungsfällen nicht viel helfen. Es ist wahrscheinlich eine schwache Idee, Kompilierungs- oder Modullade- und Laufzeitzuweisungen zu mischen, da Sie sich selbst (oder andere, die Ihren Code verwenden) verwirren. Besonders in Sprachen, die sich etwas uneinheitlich verhalten, wie es Python wohl tut.
Matanster
26

Eine Schwierigkeitsquelle bei dieser Frage ist, dass Sie ein Programm mit dem Namen haben bar/bar.py: import barimportiert entweder bar/__init__.pyoder bar/bar.py, je nachdem, wo es ausgeführt wird, was es etwas umständlich macht, zu verfolgen, welches aist bar.a.

So funktioniert es:

Der Schlüssel zum Verständnis dessen, was passiert, ist zu erkennen, dass in Ihrem __init__.py,

from bar import a

in der Tat macht so etwas

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

und definiert eine neue Variable ( bar/__init__.py:afalls gewünscht). Somit bindet Ihr from bar import ain den __init__.pyNamen bar/__init__.py:aan das ursprüngliche bar.py:aObjekt ( None). Aus diesem Grunde Sie tun können , from bar import a as a2in __init__.py: In diesem Fall ist es klar, dass Sie beide haben bar/bar.py:aund ein eindeutigen Variablennamen bar/__init__.py:a2(in Ihrem Fall die Namen der beiden Variablen nur zufällig auf beides sein a, aber sie lebt noch in einer anderen Namensraum: in __init__.py, sie sind bar.aund a).

Nun, wenn du es tust

import bar

print bar.a

Sie greifen auf eine Variable zu bar/__init__.py:a(da import barIhre importiert wird bar/__init__.py). Dies ist die Variable, die Sie ändern (auf 1). Sie berühren den Inhalt der Variablen nicht bar/bar.py:a. Also, wenn Sie es später tun

bar.foobar()

Sie rufen bar/bar.py:foobar(), die Variable zugreift aaus bar/bar.py, die nach wie vor ist None(wenn foobar()definiert ist, bindet es Variablennamen ein für alle Mal, so dass der ain bar.pyist bar.py:a, nicht anderea Variable in einem anderen definierten Modul wie könnte es viele sein aVariablen in allen importierten Module ). Daher die letzte NoneAusgabe.

Fazit: es ist am besten , jede Zweideutigkeit in zu vermeiden import bar, indem nicht alle mit bar/bar.pyModul (da bar.__init__.pymacht Verzeichnis bar/ein Paket bereits, dass Sie auch mit importieren können import bar).

Eric O Lebigot
quelle
12

Anders ausgedrückt: Es stellt sich heraus, dass dieses Missverständnis sehr einfach zu machen ist. Es ist in der Python-Sprachreferenz hinterhältig definiert: die Verwendung von Objekt anstelle von Symbol . Ich würde vorschlagen, dass die Python-Sprachreferenz dies klarer und weniger spärlich macht.

Das fromFormular bindet den Modulnamen nicht: Es geht die Liste der Bezeichner durch, sucht jeden von ihnen in dem in Schritt (1) gefundenen Modul nach und bindet den Namen im lokalen Namespace an das so gefundene Objekt .

JEDOCH:

Beim Importieren importieren Sie den aktuellen Wert des importierten Symbols und fügen ihn wie definiert zu Ihrem Namespace hinzu. Sie importieren keine Referenz, sondern effektiv einen Wert.

So erhalten Sie den aktualisierten Wert von i , müssen Sie eine Variable importieren, die einen Verweis auf dieses Symbol enthält.

Mit anderen Worten, das Importieren ist NICHT wie eine importin JAVA, eine externalDeklaration in C / C ++ oder sogar eine useKlausel in PERL.

Eher die folgende Aussage in Python:

from some_other_module import a as x

ist , wie der folgenden Code in K & R C:

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

int x = a;

(Einschränkung: Im Python-Fall beziehen sich "a" und "x" im Wesentlichen auf den tatsächlichen Wert: Sie kopieren nicht die INT, sondern die Referenzadresse.)

Mark Gerolimatos
quelle
Eigentlich finde ich den Python-Weg importviel sauberer als den von Java, weil Namespaces / Bereiche immer richtig getrennt bleiben und sich niemals auf unerwartete Weise gegenseitig stören sollen. Wie hier: Das Ändern einer Bindung eines Objekts an einen Namen in einem Namespace (lesen: etwas der globalen Eigenschaft eines Moduls zuordnen) wirkt sich niemals auf andere Namespaces (lesen: die importierte Referenz) in Python aus. Dies geschieht jedoch in Java usw. In Python müssen Sie nur verstehen, was importiert wird, während Sie in Java auch das andere Modul verstehen müssen, falls es diesen importierten Wert später ändert.
Tino
2
Ich muss ziemlich energisch widersprechen. Das Importieren / Einschließen / Verwenden hat eine lange Tradition in der Verwendung des Referenzformulars und nicht des Wertformulars in jeder anderen Sprache.
Mark Gerolimatos
4
Verdammt, ich drücke die Eingabetaste ... es gibt eine Erwartung des Imports als Referenz (gemäß @OP). Tatsächlich muss einem neuen Python-Programmierer, egal wie erfahren er ist, dies auf die Art "Ausschau halten" gesagt werden. Das sollte niemals passieren: Aufgrund der allgemeinen Verwendung hat Python den falschen Weg eingeschlagen. Erstellen Sie bei Bedarf einen "Importwert", aber verbinden Sie Symbole zum Zeitpunkt des Imports nicht mit Werten.
Mark Gerolimatos