これは、Python がコードをバイトコードに変換する方法 (コンパイル手順) に関連しています。
関数をコンパイルするとき、Python は割り当てられたすべての変数をローカル変数として扱い、最適化を実行して名前の検索回数を減らします。各ローカル変数にはインデックスが割り当てられ、関数が呼び出されると、その値はインデックスでアドレス指定されたスタック ローカル配列に格納されます。コンパイラは、変数にアクセスするためのオペコードを出力しますLOAD_FAST
。STORE_FAST
代わりに、global
構文は、変数に値が割り当てられている場合でも、ローカル変数と見なすべきではなく、インデックスを割り当てるべきではないことをコンパイラに示します。代わりにLOAD_GLOBAL
、STORE_GLOBAL
オペコードを使用して変数にアクセスします。これらのオペコードは、おそらく多くの辞書 (ローカル、グローバル) で検索を行うために名前を使用するため、処理が遅くなります。
LOAD_GLOBAL
変数が値を読み取るためだけにアクセスされる場合、コンパイラはそれがローカル変数であるかグローバル変数であるかがわからないため、常に出力し、グローバル変数であると想定します。
したがって、最初の関数で using を使用すると、書き込みアクセスをローカル変数ではなくグローバル変数への書き込みとしてglobal x
処理するようにコンパイラーに通知します。x
関数のオペコードはそれを明確にします:
>>> dis.dis(changeXto1)
3 0 LOAD_CONST 1 (1)
3 STORE_GLOBAL 0 (x)
6 LOAD_CONST 0 (None)
9 RETURN_VALUE
__main__
3 番目の例では、モジュールをという名前のローカル変数にインポートして__main__
から、そのx
フィールドに代入します。モジュールはすべての最上位マッピングをフィールドとして格納するオブジェクトであるためx
、__main__
モジュール内の変数に割り当てています。ご覧のとおり、コードはモジュールで定義されているため、__main__
モジュール フィールドはディクショナリの値に直接マップされます。オペコードは、直接アクセスしないことを示しています。globals()
__main__
x
>>> dis.dis(changeXto3)
2 0 LOAD_CONST 1 (-1)
3 LOAD_CONST 0 (None)
6 IMPORT_NAME 0 (__main__)
9 STORE_FAST 0 (__main__)
3 12 LOAD_CONST 2 (3)
15 LOAD_FAST 0 (__main__)
18 STORE_ATTR 1 (x)
21 LOAD_CONST 0 (None)
24 RETURN_VALUE
2 番目の例は興味深いものです。変数に値を割り当てるためx
、コンパイラはそれがローカル変数であると想定し、最適化を行います。次に、はモジュールをfrom __main__ import x
インポートし、モジュール内のの値をという名前のローカル変数に__main__
バインドする新しいバインディングを作成します。これは常に当てはまります。現在の名前空間に新しいバインディングを作成するだけです。変数に新しい値を代入すると、関連のないモジュール内のバインディングではなく、現在のバインディングが変更されるだけです(ただし、値が変更可能で、それを変更すると、変更はすべてのバインディングを通じて表示されます)。オペコードは次のとおりです。x
__main__
x
from ${module} import ${name}
x
__main__
>>> dis.dis(f2)
2 0 LOAD_CONST 1 (-1)
3 LOAD_CONST 2 (('x',))
6 IMPORT_NAME 0 (__main__)
9 IMPORT_FROM 1 (x)
12 STORE_FAST 0 (x)
15 POP_TOP
3 16 LOAD_CONST 3 (2)
19 STORE_FAST 0 (x)
22 LOAD_CONST 0 (None)
25 RETURN_VALUE
これについて考える良い方法は、Python ではすべての割り当てが名前を辞書内の値にバインドし、逆参照は単に辞書検索を行うことです (これは大まかな概算ですが、概念モデルにかなり近いものです)。を実行すると、キーobj.field
の隠し辞書obj
( 経由でアクセス可能obj.__dict__
) が検索され"field"
ます。
ネイキッド変数名がある場合は、locals()
辞書で検索され、globals()
異なる場合は辞書で検索されます (コードがモジュール レベルで実行される場合は同じです)。locals()
割り当ての場合、グローバル アクセスが必要であると宣言しない限り、バインディングは常にディクショナリに配置されますglobal ${name}
(この構文はトップレベルでも機能します)。
したがって、関数を翻訳すると、これはほとんど次のようになります。
# NOTE: this is valid Python code, but is less optimal than
# the original code. It is here only for demonstration.
def changeXto1():
globals()['x'] = 1
def changeXto2():
locals()['x'] = __import__('__main__').__dict__['x']
locals()['x'] = 2
def changeXto3():
locals()['__main__'] = __import__('__main__')
locals()['__main__'].__dict__['x'] = 3