ref(えいご) ref(にほん語)
- closureの基本 Closure = function + environment
- Closureとアクセサ 次に、クラスオブジェクト的な操作を模倣することを考える
- クロージャとクラス変数
- まとめ
これをOOPの文脈で置き換えると
Class = Method + member(attributes)
Closureのほうはあくまで関数が主役。
次のPythonのコードは典型的なClosureの応用
def fibonacci_factory():
memo = {}
def fibonacci(n):
if n in memo: return memo[n]
if n < 1: return 1
memo[n] = fibonacci(n-1) + fibonacci(n-2)
return memo[n]
return fibonacci
ファクトリの呼び出しにより、fibonacci関数が作成され関数は内部的に memo というローカル変数への参照を保持し続ける。pythonでは、__closure__ attribute を用いて内包している環境を見る事ができる。
f = fibonacci_factory() f(100) # -> 927372692193078999176 print(f.__closure__) # -> tuple of cell object print(f.__closure__[0].cell_contents) # -> function oject of f itself print(f.__closure__[1].cell_contents) # -> might be a dictionary環境が、cell object のタプルとして保存されているのをみることができる。
もう一度ファクトリを呼び出すと異なる環境を保持するfibonacci関数が作成される。
g = fibonacci_factory() id(g.__closure__[0]) == id(f.__closure__[0]) # -> falsef と g では各々別々のメモ辞書を持ってるのが分かる。
別の例として、呼び出す毎に内部変数が1ずつ増えていくカウンターを考える
def counter_factory():
x = 0
def _counter():
nonlocal x
x += 1
print(x)
return _counter
上の nonlocal せんっげんはpython3から新しく追加されたもので、これを付けないと、下の様なエラーになる。
counter_factory()() --------------------------------------------------------------------------- UnboundLocalError Traceback (most recent call last)ファクトリ内部関数での x へのアサインメントは上手くいかない。というのはスコープ外の変数への代入だからである。in () ----> 1 counter_factory()() in _counter() 2 x = 0 3 def _counter(): ----> 4 x += 1 5 print(x) 6 return _counter UnboundLocalError: local variable 'x' referenced before assignment
従って、x は一つ上のスコープの変数なんですよ、という宣言が必要になる。
ちなみに、attribute を用いる事でnonlocal宣言を回避して、クロージャっぽいものを作る事もできる。
def counter_factory():
def _counter():
_counter.x += 1
print(_counter.x)
_counter.x = 0
return _counter
例えば、オブジェクトであればアクセサやらプロパティを用いて、内部で保持している変数(attributes)を変更できる。
同じ事をクロージャでやってみたいが、__closure__ attribute は tuple なので直接変更できない。
上で用いた nonlocalせんっげん を使って、アクセサ関数を作って一緒に返してやればよい
def counter_factory():
x = 0
def _counter():
nonlocal x
x += 1
print(x)
def set_count(new_x):
nonlocal x
x = new_x
def get_count():
return x
return (_counter, set_count, get_count)
get_countには nonlocal は必要ない。単に参照するだけだからである正直こういった使い方は邪道な気がする
一部の環境を他のクロージャと共有したい場合があるでありそう
クラスであれば、単にクラス変数を用いれば実現できるが、クロージャだとどうなるだろう?
そのときはファクトリのファクトリを作って、共有したい変数を第一レベルに置いて各々の関数で保持したい変数を第二レベルにおけばよい。
大まかな作りは次のようになるはず
def factory_factory(x):
share_var = ... # some collections
def _factory(y):
each_vars = ... # some other collections
def __process(z):
someprocesses
return __process
return _factory
こうしてやると、x, share_var は全体で共有することになって、y, each_vars は各々で保持する応用として、二つのカウント値を持ったカウンタを作る。
一つは全体で共有するカウント値で、もう一つは各々別々に持つカウント値である
def counter_fact_fact():
whole_count = 0
def _counter_fact():
_count = 0
def __counter():
nonlocal _count, whole_count
_count += 1
whole_count += 1
return (_count, whole_count)
return __counter
return _counter_fact
アンダーバーが多くて醜い(^q^)実際に呼び出して使ってみると、ちゃんと動いくうぃ(^q^)
cf = counter_fact_fact() f = cf() g = cf() f() # -> (1, 1) g() # -> (1, 2) f() # -> (2, 3) g() # -> (2, 4)
- 単なるメモ化程度ならばクロージャは便利だし、デコレータと組み合わせると色々な操作がシンプルに書ける
- 継承をclosureで実装するにはどうすればいいだろうか?
- 基本的にpythonはOOPLなので、無理せずクラスを作った方が良い場合が殆どだろう
- どのようにしてクラスオブジェクトとクロージャを使い分けてやればいいのか明確な判断基準が欲しいところ
- ブログかくのめんどい(^q^)
0 件のコメント:
コメントを投稿