お久しぶりです。
最近はPython人気ですね。利用者も増えてきたので、スコープについて知っている限り書いていこうと思います。
関数とか変数がわからない方には厳しいので一旦別のチュートリアルとかを読んでくることをオススメします。
この記事では諸事情によりPython 3.5.1
と 3.6.1
を使って動作検証してます。
スコープとは
スコープとは変数の有効な範囲です。
Pythonのスコープは 4つ
にわけられ、その頭文字をとって LEGB
と言われています。
それぞれ Local scope
, Enclosing (function's) scope
, Global Scope
, Built-in scope
です。
イメージ的には、左に行くほど 狭く強く 右に行くほど 広く弱い です。 強いとか弱いとかは何かというとは 優先度 なわけですが、これは後述します。
まずはそれぞれのスコープの特性を説明します。
ローカルスコープ
ローカルスコープが指す範囲は関数の中です。 変数がローカルスコープに属する条件は「関数内で定義された」場合のみです。
定義と一口に言っても記述方法はいくつかあります。言葉だけでは伝わらない思いがあるので実際に動かしてみましょう。
たとえば、こんな謎の関数があるとします。
def something(a, b=1, *c, **d): e = 2 def f(): pass # 空の関数定義 class g: pass # 空のクラス定義 import os as h # モジュールのインポート print(locals()) # locals はローカルスコープの変数を取得する関数
この関数の実行結果は以下のような感じになるんじゃないでしょうか。
※辞書の順番や、モジュールパス、アドレスなどは環境毎に異なります。
>>> something(0) # 見やすいように整形したけど普通はこんなふうに表示されないので注意 { 'a': 0, 'b': 1, 'c': (), 'd': {}, 'e': 2, 'f': <function something.<locals>.f at 0x10eda2730>, 'g': <class '__main__.something.<locals>.g'>, 'h': <module 'os' from '/usr/local/Cellar/python3/3.5.1/Frameworks/Python.framework/Versions/3.5/lib/python3.5/os.py'>, }
a
, b
, c
, d
は関数の引数として定義された変数です。
どんな引数であっても属するスコープは同じです。
e
は代入によって定義された変数ですね。 f
は関数の定義です。 g
はクラスの定義です。
意外ですか?関数やクラスの定義もその例外ではありません。
h
はインポートしたモジュールです。これも定義なのです。
(from import
であっても同じです)
上記はすべてローカルスコープに属する変数、つまりローカル変数です。
関数外からアクセスするともれなく NameError
になるはずです。
ならなければ すでに定義されている ことを疑ってみてください。
>>> b Traceback (most recent call last): File "", line 1, in NameError: name 'b' is not defined
グローバルスコープ
さて、 LEGB
の順番で行けば次は
エンクロージングスコープ
のはずですが、あいつは少し難しいので最後にします。
他の言語を経験しているとどこからでも参照できる変数が属するスコープだろうと考えてしまいそうですが、 実はPythonのグローバルスコープはそんなに広くありません。
どこまでかといえば、モジュール(ファイル)です。他のモジュールから変数を参照したい場合はインポートが必要になります。
同じモジュール(ファイル)のグローバルスコープに書かれた変数をグローバル変数と呼ぶのに対し、 別モジュールに書かれたグローバル変数をモジュール変数と呼び、モジュールオブジェクトの属性として参照できます。
モジュール経由で参照できる変数はグローバル領域に書かれたものだけというわけですね。 よく考えれば当たり前のことでしたね。
たとえばこういうモジュールがあったとすれば、グローバルスコープに属するのは b
, c
です。
b = 1 def c(d): e = 2
このモジュールに a.py という名前をつけ、インポートしてみましょう。
>>> import a >>> dir(a) # dir は対象オブジェクトの属性を表示する関数(__で囲まれてるのは特殊な属性なので無視して良い) ['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'b', 'c'] >>> a.b 1 >>> a.c(2) >>> globals() # globals()を使うことでグローバル変数一覧を取得できる。 { '__package__': None, '__loader__': , '__builtins__': , '__doc__': None, '__spec__': None, '__name__': '__main__', 'a': , }
a
モジュールのモジュール変数として b
, c
が登録されているのがわかりますね。
一回定義したらどこからでも参照できる、そんな便利な変数はPythonにはないのでした。 定義したら逐一インポートしてあげましょう。
この辺がちゃんと理解できているとユニットテストで処理をパッチするということがどういうことか理解しやすくなります。
- info
- 先程から使っている対話モード(
>>>
で始まってるコード) に書いたコードはグローバルスコープに属します。
- 先程から使っている対話モード(
ビルトインスコープ
ビルトインスコープに属する変数はどこからでも参照できます。
いやいや、上でどこからも参照できる変数は定義できないって言ったじゃんということになるのですが、 ビルトインスコープに対して変数を追加定義することは基本的にできません。
このスコープに属する変数は定義されていなくとも、どこからでも使うことができます。
変数と言いましたが、その多くは関数(とクラス)です。これらの関数はビルトイン関数とか組み込み関数と呼ばれます。
- warning
- 予約語とは違うので混同しないように注意しましょう。
- defとか class とかの構文のキーワードが予約語です
int
, str
, len
, range
などのよく使う関数から先程使った
locals
, globals
も全部ビルトイン関数です。(一部はクラスでもあります)
- info
(ここは余裕がある方だけ読んでくれればOK)
先程ビルトインスコープに変数を追加することは「基本的」にできないと言いましたが実はできます。
実はビルトインスコープはモジュールとして存在しているのです。
その実体は
builtins
モジュール です。 このbuiltins
モジュールに属性を追加するとどこからでも参照できちゃうのです。>>> import builtins # python3.? からインポートできるよ! >>> test # まだ定義されてないから参照できない Traceback (most recent call last): File "", line 1, in NameError: name 'test' is not defined >>> dir(builtins) ['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip'] >>> builtins.test = 123 >>> test # ビルトインスコープに登録すると参照できた 123 >>> builtins is __builtins__ # 実はグローバルスコープに __builtins__ という名前で置かれてる True
注) できるとはいえ、これは裏技的な感じなのでよいこの皆さんはやめましょう。
エンクロージングスコープ
Enclosing function's scope
でもOKです。(function's
はなくてもよさそうです)
出鼻をくじくようですが、前述の通りこのスコープが一番ややこしいです。正直わからなければ飛ばしてしまってもいいと思いますん。
端的にいうと関数の外側のローカルスコープです。 関数の外側は関数じゃねーだろって?いやいや、実は関数の場合もあるんですよ。
関数の中で関数は定義できますし、クラスだって定義できます。このスコープは関数の中で関数が定義されている、そんな場合に初めて意識するスコープということです。
こんな感じにキモくネストしている 関数a
があるとします。
g = 1 def a(b): c = 3 def aa(bb): def aaa(bbb): ccc = 333 print('this is aaa', locals()) print('c:', c, 'cc:', cc, 'g:', g, 'gg:', gg) cc = 33 print('this is aa', locals()) aaa(222) print('this is a', locals()) aa(22) gg = 11
関数の最深部、 aaa関数
では外側の関数内で定義された 変数
c
, cc
をそれぞれ参照しています。
これを実行するとこんな感じになります。
>>> a(2) this is a {'b': 2, 'aa': <function a.<locals>.aa at 0x10dd1f1e0>, 'c': 3} this is aa {'bb': 22, 'aaa': <function a.<locals>.aa.<locals>.aaa at 0x10dd1f268>, 'cc': 33, 'c': 3} this is aaa {'bbb': 222, 'ccc': 333, 'c': 3, 'cc': 33} c: 3 cc: 33 g: 1 gg: 11
解説する前に、ちょっと視覚的にわかりやすく表現してみましょう。
aaa
を基点として考えるとaaa
がローカルスコープaa
,a
は エンクロージングスコープ です。aa
にとっての エンクロージングスコープはa
です。a
にとっての エンクロージングスコープは存在しません。
- global
g = 1
- a
b = 2
c = 3
- aa
bb = 22
- aaa
ccc = 333
cc = 33
local より外側の関数が エンクロージングスコープ となり、
b
, c
, bb
, cc
は参照できます。
もちろん g
, gg
も参照できますが、これらはグローバルスコープです。
大事なのは 定義された順番 ではなく 定義されているかどうか だけです。
cc
は参照元の関数 aaa
より後で定義されていますが、
呼出より前で定義されているため参照できます。
気になる人は、試しに aaa(222)
より後で cc
を定義してみてください。
- error
- NameError: free variable 'cc' referenced before assignment in enclosingscope
こんなエラーが出るはずです。
また、 c
, cc
が aaa関数
のローカル変数となっていることに気づいたでしょうか。
これは自分も知らなかったんですが、 どうやらエンクロージングスコープで参照した変数はローカル変数としても扱われるようです。
このエンクロージングスコープはクロージャとして利用されます。 クロージャって何?って方はこの記事を最後まで読むかググってみましょう。
優先度
狭いスコープほど優先度が高い、最初にそんな話をしました。
Pythonのスコープは階層になっていると考えるとわかりやすいです。
例えば、こんなプログラムがあったとします。
id = 1 int = 1 a = 1 b = 1 def outer(): id = 2 a = 2 def inner(): id = 3 range = 3 # この記法ができるのは Python3.6 から print(f'id:{id}, len:{len}, int:{int}, range:{range}, a:{a}, b:{b}') inner()
これを表に直すと以下のようになりました。(全部ではないけど)
表を下から覗き込み、一番下に該当する値が参照されている様子がわかりますか。
変数名 | id | len | int | range | a | b |
---|---|---|---|---|---|---|
Built-in | id関数 | len関数 | int関数 | range関数 | ||
Global | 1 | 1 | 1 | 1 | ||
Enclosing | 2 | 2 | ||||
Local | 3 | 3 |
プログラムを実行すると表の通りに値が参照されていますね。
>>> outer() id:3, len:<built-in function len>, int:1, range:3, a:2, b:1
ちなみにどのスコープにも該当しない変数を参照するとNameErrorが発生します。
さて、もう少し続くんじゃ。
global文とnonlocal文
先程、関数内で定義された変数はすべてローカルスコープに属するという話をしました。
そうです。Pythonは代入文があると、加算代入だろうが利用者の意図と反してローカル変数だと判断されてしまうのです。
ローカル変数の優先度は一番強いので、存在していないローカル変数に加算して代入しようとしていると判断され
UnboundLocalError
が発生します。
g = 1 def a(b): g += b print(g)
>>> a(2) Traceback (most recent call last): File "", line 1, in File "", line 2, in a UnboundLocalError: local variable 'g' referenced before assignment
困りました。
私はただ グローバル変数 に 2 を足したいだけなのに
UnboundLocalError
って何なの?
というときに使うのが global文
です。
g = 1 def c(d): global g g += d print(g)
>>> c(2) 3 >>> g 3
これで平和が訪れました。
global文
を使うことで関数スコープ中で変数への代入があっても、
それはローカル変数ではなくグローバル変数であると解釈されます。
- info
UnboundLocalError
とは ローカル変数として定義される予定だけど、まだ定義はされていない状態で参照しようとしたというNameError
の一種です。 (実際に継承してるのでNameError
でもキャッチできます)定義される予定ってなんだよって感じですね。 Pythonは関数が定義された時点でその関数に属するローカル変数を決定します。
ローカル変数が定義された瞬間ではありません
一つ前の例で作った関数を見てみると
>>> a.__code__.co_varnames ('b', 'g') >>> c.__code__.co_varnames ('d',) # g は global文の指定により ローカル変数ではなくなった
こんなふうに、関数の外側からどんなローカル変数が定義されるか見えます。 楽しいですね?
nonlocalも概ね同じですが、これが活躍するのはエンクロージングスコープの変数を書き換えたいとき、
一番多いケースはやはり クロージャ
でしょう。
クロージャ
人によってはこの言葉を聞いたことがあると思います。というか聞いたことがある人はすでにわかったかもしれません。
クロージャとは変数を外側の関数、つまりエンクロージングスコープに閉じ込めた関数です。
こんな事をして何が嬉しいか?
数の合計を記憶する関数 pile
を作って考えてみましょう。
def pile_factory(start=0): def pile(num): nonlocal start # nonlocalの指定が必須 start += num print(start) return pile
クロージャは 外側
と 内側
に定義された2つの関数からなり、 内側の関数
で 外側の関数
の変数を利用します。
この変数を閉じ込める行為を 変数を束縛する
とも言ったりもします。
外側の関数を返却する関数はその性質から ファクトリ関数
とも呼ばれ、今回の例では pile_factory
がそれに当たります。
>>> pile1 = pile_factory(2) >>> pile2 = pile_factory(3) >>> pile1(3) 5 # 2 + 3 >>> pile2(4) 7 # 3 + 4 >>> pile1(4) 9 # 2 + 3 + 4 >>> pile2(5) 12 # 3 + 4 + 5
重要なのは pile_factory
によって作成された
pile1
, pile2
という関数それぞれが違う状態を保持しているということです。
普通の関数は状態を持ちませんからね。
あとエンクロージングスコープに閉じ込められた変数はpile関数以外から参照することができません。
この2点は大きなメリットです。
クロージャの応用にデコレータという技術があります。デコレータについて知りたい方は こちら
少し話を戻します。
pile関数の中で、pile_factoryの引数(エンクロージングスコープの変数)を書き換えるため、nonlocalの指定が必要になります。
気になる方は nonlocal 文を外して UnboundLocalError
が発生することを確認してみましょう。
この global文
と nonlocal文
は対象スコープ以外にもう一つ違いがあります。
global文
はまだグローバルスコープに定義されていないものも global文
に指定できるのに対し、
nonlocal文
はエンクロージングスコープに定義されていないものを指定できません。
g = 1 def a(): global g, h print(g, h) def b(): c = 1 def bb(): nonlocal c, d print(c, d)
こういうコードを書くと b
関数を定義した瞬間に
SyntaxError: no binding for nonlocal 'd' found
と言われてしまいます。
グローバルスコープには後から変数を追加できますが、 エンクロージングスコープには追加できませんから当然といえば当然の挙動ですね。
また global文
で同じ名前の変数を指定していると同様に SyntaxError
になるようです。
SyntaxError: name 'start' is nonlocal and global
でもSyntaxErrorて.. ちなみに上記の a関数
はこんな感じの挙動になります。
>>> a() Traceback (most recent call last): File "", line 1, in File "", line 3, in a NameError: name 'h' is not defined >>> h = 2 # h を定義したら.. >>> a() 1 2
これらの文について詳しく知りたい方は以下を参照
del 文
del
という文を使うことで指定した変数を、参照可能な一番上のスコープから削除できます。
>>> print(range) <class 'range'> >>> range = 1 >>> print(range) 1 >>> del range >>> print(range) <class 'range'>
この場合はビルトイン関数のrangeをマスクして(隠して)しまったグローバル変数を削除しています。
この考え方でいくと、
- ローカルスコープ
- エンクロージングスコープ
- グローバルスコープ
- ビルトインスコープ
のように順に削除できそうに見えるんですが、ローカル変数として定義した時点で外側は見えないらしくうまくいきません。
また、ビルトインスコープの変数も del
で消すことはできませんでした。
(普通はやらないけど気になるやつ)
以上です。
何かおかしいところがあったらやさしく教えてください。