モジュール変数を別のモジュールから変更するにはどうすればよいですか?


106

という名前のパッケージがbarあり、次のものが含まれているとしますbar.py

a = None

def foobar():
    print a

__init__.py

from bar import a, foobar

次に、このスクリプトを実行します。

import bar

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

これが私が期待することです:

None
1
1

ここに私が得るものがあります:

None
1
None

誰かが私の誤解を説明できますか?

回答:


103

を使用していfrom bar import aます。aインポートモジュールのグローバルスコープ(またはインポートステートメントが発生するスコープ)のシンボルになります。

新しい値をに割り当てると、実際の値ではなく、aどの値aも変更するだけです。in を使用してbar.py直接インポートしimport bar__init__.pyを設定して実験を行いますbar.a = 1。このようにして、実際にこのコンテキストbar.__dict__['a']での「本当の」値である変更を行いaます。

これは3つのレイヤーで少し複雑ですが、実際にから派生したと呼ばれるモジュールののbar.a = 1値を変更します。これは、の値が変更されないので、見ている実際のファイルでの生活を。変更したい場合は設定できます。abar__init__.pyafoobarfoobarbar.pybar.bar.a

これはfrom foo import barimportステートメントの形式を使用する際の危険の1つですbar。2つのシンボルに分割されます。1つはグローバルに表示され、内部fooからは元の値を指し、別のシンボルはimportステートメントが実行されるスコープで表示されます。シンボルが指す場所を変更しても、それが指している値は変わりません。

この種のものはreload、インタラクティブインタープリタからモジュールを作成しようとするときにキラーです。


ありがとう!あなたの答えはおそらく私を犯人を探すための数時間を救いました。
jnns

bar.bar.aほとんどのユースケースでは、このアプローチでもあまり役に立たないようです。自分自身(またはコードを使用する他のユーザー)を混乱させることになるので、コンパイルまたはモジュールのロードとランタイムの割り当てを混在させるのはおそらく弱い考えです。特に、Pythonが間違いなくそうであるように、それについて多少一貫性のない動作をする言語では。
matanster

26

この質問の難しさの一つの原因は、あなたが名前のプログラムを持っていることですbar/bar.pyimport bar輸入のいずれかbar/__init__.pyまたはbar/bar.pyそれを追跡するために少し面倒になりれ、行われている場所に応じて、aですbar.a

以下にその仕組みを示します。

何が起こるかを理解するための鍵は__init__.py

from bar import a

実際には次のようなことをします

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

新しい変数を定義します(bar/__init__.py:a必要に応じて)。したがって、from bar import ain __init__.pybar/__init__.py:a元のbar.py:aオブジェクト(None)に名前をバインドします。あなたがすることができる理由はここにあるfrom bar import a as a2__init__.py:この場合には、あなたが両方を持っていることは明らかであるbar/bar.py:a明確な変数名はbar/__init__.py:a2(あなたのケースでは、2つの変数の名前はちょうど両方のことが起こるaが、異なる名前空間で、彼らはまだ生きて:中__init__.py、それらはbar.aありますa)。

今、あなたがするとき

import bar

print bar.a

変数にアクセスしていますbar/__init__.py:a(をimport barインポートしているためbar/__init__.py)。これは、(1に)変更する変数です。変数の内容には触れていませんbar/bar.py:a。だからあなたがその後するとき

bar.foobar()

あなたはを呼び出してbar/bar.py:foobar()aからbar/bar.pyまだ変数にアクセスしますNonefoobar()定義されている場合、変数名をバインドするので、他のモジュールで定義されている他の変数abar.pybar.py:aなく、in aです- aインポートされたすべてのモジュールに多くの変数がある可能性があるため))。したがって、最後のNone出力です。

結論:モジュールを持たないことでimport bar、の曖昧さを回避するのが最善です(ディレクトリをすでにパッケージにしているため、でインポートすることもできます)。bar/bar.pybar.__init__.pybar/import bar


12

言い換えると、この誤解は非常に簡単に起こります。 Python言語リファレンスでこっそりと定義されています:シンボルの代わりにオブジェクトを使用します。Python言語リファレンスを使用すると、これがより明確になり、まばらにならないようにすることをお勧めします。

fromフォームはバインドモジュール名はありません:それは識別子のリストを通過するには、モジュールでそれらの一つ一つを見上げステップ(1)で見つかった、とのローカル名前空間に名前をバインドするオブジェクトこのようにして求めました。

しかしながら:

インポートするときは、インポートされたシンボルの現在の値をインポートし、定義どおりにネームスペースに追加します。 参照をインポートするのではなく、値を効果的にインポートします。

したがって、の更新された値を取得するにはi、そのシンボルへの参照を保持する変数をインポートする必要があります。

つまり、インポートはimport、JAVAのexternalC、C / C ++の宣言、またはusePERLの句とは異なります。

むしろ、Pythonでの次のステートメント:

from some_other_module import a as x

以上あるような K&R Cに以下のコード:

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

int x = a;

(注意:Pythonの場合、「a」と「x」は基本的に実際の値への参照です。INTをコピーするのではなく、参照アドレスをコピーします)


実際、import名前空間/スコープは常に適切に分離され、予期しない方法で相互に干渉しないため、Pythonの方がJavaよりはるかにクリーンであることがわかります。このように:オブジェクトのバインディングをネームスペース内の名前に変更(読み取り:モジュールのグローバルプロパティに何かを割り当てる)しても、Pythonの他のネームスペース(読み取り:インポートされた参照)に影響を与えることはありません。しかし、Javaなどではそうです。Pythonではインポートされたものを理解するだけでよく、Javaでは、インポートされた値が後で変更される場合に備えて他のモジュールも理解する必要があります。
Tino

2
私はかなり精力的に反対しなければなりません。インポート/インクルード/使用には、参照フォームを使用してきた長い歴史があり、他のすべての言語についての値のフォームではありません。
Mark Gerolimatos 2017年

4
くそー、私はリターンをヒットします...これは参照によるインポートの期待があります(@OPに従って)。実際、新しいPythonプログラマーは、どんなに経験があっても、「見張り」の方法でこれを知らされなければなりません。それは決して起こらないはずです:一般的な用法に基づいて、Pythonは間違った道をたどりました。必要に応じて「インポート値」を作成しますが、シンボルをインポート時の値と混同しないでください。
Mark Gerolimatos 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.