『Effective Python』の続き。Pythonのスコープを勉強しなおした。 www.effectivepython.com
クロージャを理解しようとして書いたのが次の実用性に欠けるコードである。
def outer(out): def middle(mid): def inner(inn): print("In inner", out) print("In inner", mid) print("In inner", inn) inner('a') print("In midle", out) print("In midle", mid) # print("In inner", inn) middle('b') print("In outer", out) # print("In midle", mid) # print("In inner", inn) outer('c')
実行結果は次の通り。
In inner c In inner b In inner a In midle c In midle b In outer c
なお、コメントを外すとNameError
が発生する。
この辺の動きは『Python文法詳解』を読むのが手っ取り早い。
- 作者: 石本敦夫
- 出版社/メーカー: オライリージャパン
- 発売日: 2014/09/18
- メディア: 大型本
- この商品を含むブログ (2件) を見る
関数の内部で定義した関数は、外側の関数のローカル変数も参照できます。関数が何重にネストしていても、もっとも内側の関数から、外側の関数全てのローカル変数を参照できます。 内側の関数から外側の変数を参照したとき、その値は、内側の関数を定義したと時の値ではなく、内側の関数が呼び出され、実際に変数を参照した時の値になります。(P211)
では、次の例を見てみる。『Effective Python』の例である あるグループに属する整数が優先されるように整数のリストをソートしたい、というシチュエーションを踏まえた例である。
def sort_priority2(numbers, group): found = False def helper(x): if x in group: found = True # Seems simple return (0, x) return (1, x) numbers.sort(key=helper) return found numbers = [8, 3, 1, 2, 5, 4, 7, 6] group = {2, 3, 5, 7} found = sort_priority2(numbers, group) print('Found:', found) print(numbers)
実行結果は
Found: False [2, 3, 5, 7, 1, 4, 6, 8]
となり、間違った結果となっている。
これも、『Python文法詳解』が役に立つ。『Effective Python』にもあるが。
関数外の名前空間で定義された自由変数は、内側の関数で上書きすることが出来ません。(P212)
ここでいう上書きというのがはっきりしないなあと思ったのが、Python 2の常套手段である次のコード。sort_priority2
を改良したものである。Python 3でも動作する。
def sort_priority(numbers, group): found = [False] def helper(x): if x in group: found[0] = True return (0, x) return (1, x) numbers.sort(key=helper) return found[0]
この場合、上書きできないのはfound
がリストを参照していることであり、そのリスト内を書き換えることは出来る、と理解した。
なお、Python 3向けの解決方法は本文を見ていただくとする。
『Effective Python』の最後に、
This approach also works when the variable used to traverse the scope is a dictionary, a set, or an instance of a class you're defined.
とあるが、これは上記のPython 2向けアプローチはリストの他に辞書、集合、ユーザ定義のクラスのインスタンスに有効ですよ、という話を上記の例をリストから辞書や集合に置き換えても成立しますよ、という意味と勘違いして1日困っていた。ああ自分の読解力に不安がよぎる…。