2017-09-25

Pythonのスコープについて

お久しぶりです。

最近はPython人気ですね。利用者も増えてきたので、スコープについて知っている限り書いていこうと思います。

関数とか変数がわからない方には厳しいので一旦別のチュートリアルとかを読んでくることをオススメします。

この記事では諸事情によりPython 3.5.13.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にはないのでした。 定義したら逐一インポートしてあげましょう。

この辺がちゃんと理解できているとユニットテストで処理をパッチするということがどういうことか理解しやすくなります。

[python] まだmockで消耗してるの?mockを理解するための3つのポイントPythonのユニットテストにはなくてはならない Mock。概念と使い方についてじっくりと解説します。https://note.crohaco.net/2015/python-mock/

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, ccaaa関数 のローカル変数となっていることに気づいたでしょうか。

これは自分も知らなかったんですが、 どうやらエンクロージングスコープで参照した変数はローカル変数としても扱われるようです。

このエンクロージングスコープはクロージャとして利用されます。 クロージャって何?って方はこの記事を最後まで読むかググってみましょう。

優先度

狭いスコープほど優先度が高い、最初にそんな話をしました。

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()

これを表に直すと以下のようになりました。(全部ではないけど)

表を下から覗き込み、一番下に該当する値が参照されている様子がわかりますか。

変数名idlenintrangeab
Built-inid関数len関数int関数range関数
Global1111
Enclosing22
Local33

プログラムを実行すると表の通りに値が参照されていますね。

>>> 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をマスクして(隠して)しまったグローバル変数を削除しています。

この考え方でいくと、

  1. ローカルスコープ
  2. エンクロージングスコープ
  3. グローバルスコープ
  4. ビルトインスコープ

のように順に削除できそうに見えるんですが、ローカル変数として定義した時点で外側は見えないらしくうまくいきません。

また、ビルトインスコープの変数も del で消すことはできませんでした。 (普通はやらないけど気になるやつ)

以上です。

何かおかしいところがあったらやさしく教えてください。