LoginSignup
10
8

More than 5 years have passed since last update.

クロージャの実装

Last updated at Posted at 2014-07-04

前回の記事(スコープの見え方について調べてみた)の記事の内容を踏まえた上で、クロージャの実装について見てみる。

def counter(x):
    pprint(locals())
    def count():
        pprint(locals())
        y = x
        x += 1
        return y
    return count
c = counter(10)
print c()
print c()
print c() 

は、動きそうに見えるが、動かすと、次のようにエラーになる。

実行結果
{'x': 10}
{}
Traceback (most recent call last):
  File "tes004.py", line 12, in <module>
    print c()
  File "tes004.py", line 7, in count
    y = x
UnboundLocalError: local variable 'x' referenced before assignment

関数counter()の引数xは、counter()のローカル変数として見える。
内部関数count()から変数xを参照だけするだけであれば、変数xはcount()のローカル変数として参照できるが、代入コード(x += 1⇒これは参照のコードでもある)があるため、count()関数ブロックで定義される変数として動こうするが、代入の前に参照が実行行われるためエラーとなる。
外側の関数で定義された変数の参照はできるが、代入(書き換え)はできない。

次は、上手く実行できるパターン。

def counter(x):
    pprint(locals())
    y = [x]
    def count():
        pprint(locals())
        z = y[0]
        y[0] += 1
        return z
    return count
c = counter(10)
print c()
print c()
print c() 
実行結果
{'x': 10}
{'y': [10]}
10
{'y': [11]}
11
{'y': [12]}
12

このコードも、内部関数count()で変数yを書き換えているように見えるが、変数yで参照できるリストの中身を書き換えているだけなので、正常に動く。
例えば、y[0] += 1y = [y[0] + 1]のようにすると、エラーになる。これは変数yを書き換えようとしているため。
このため、参照先がリストでなく、クラスとかであっても、参照先を更新することはできる。

次は、クラスで実装した例。

class tcls():
    def __init__(self, x):
        self.x = x

def counter(x):
    pprint(locals())
    y = tcls(x)
    def count():
        pprint(locals())
        z = y.x
        y.x += 1
        return z
    return count
c = counter(10)
print c()
print c()
print c() 
実行結果
{'x': 10}
{'y': <__main__.tcls instance at 0x0222F788>}
10
{'y': <__main__.tcls instance at 0x0222F788>}
11
{'y': <__main__.tcls instance at 0x0222F788>}
12
10
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
8