rcmdnk's blog

Callback

Pythonでcallbackを使う時に引数を取り扱う方法について。

Pythonでcallback

Pythonでcallbackを使う方法として最も簡単な形としては以下の様な感じ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> class CallbackExecutor():
...
...     def __init__(self, callback):
...         self.callback = callback
...
...     def execute(self):
...         self.callback('execute')
...
... def echo(v):
...     print 'echo: %s' % v
...
>>> a = CallbackExecutor(echo)
>>> a.execute()
echo: execute

echoという関数を作ってそれをCallbackExecutorというクラスの オブジェクトに渡しています。

CallbackExecutorの中ではこの関数を保持していてexecuteを呼ぶと その中でコールバックを呼ぶようになっています。

callbackに引数を渡す

ここで、コールバックの関数に別の引数を渡して、 引数によって動作を変えたいことがあります。

1
2
def echo(v, name='defaultName'):
    print '%s: %s' % (name, v)

こんな関数を作ってその場その場でnameを変えたい時。

ただし、コールバックを渡す時に()を付けて実行してしまうと 渡すものが関数を実行した結果になってしまうので上手く行きません。

1
2
3
4
5
6
7
>>> a = CallbackExecutor(echo('any', 'myName'))
myName: any
>>> a.execute()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in execute
TypeError: 'NoneType' object is not callable

元のクラスの方で別の変数を取り入れる様にしてあげる方法もありますが、 直接関数に渡す方法として、 lambda式を使う方法があります。

1
2
3
>>> a = CallbackExecutor(lambda x: echo(x, 'myName'))
>>> a.execute()
myName: execute

callbackへ渡す引数に変数を使う時の注意

ただし、ここでmyNameの所に変数を使う時には注意が必要です。

1
2
3
4
5
6
7
8
9
10
11
>>> a = {}
>>> for name in ['myName', 'yourName']:
...     a[name] = CallbackExecutor(lambda x: echo(x, name))
...     a[name].execute()
...
myName: execute
yourName: execute
>>> a['myName'].execute()
yourName: execute
>>> a['yourName'].execute()
yourName: execute

と、最初にCallbackExecutorのオブジェクトを作った直後には きちんとmyNameが使われていますが、 一度ループを終わった後にもう一度使ってみると 両方共yourNameになっています。

これは、渡されてるのはlambdaで定義された式なので、 この部分ではnameは解決されていない状態です。

従って、execute()が実行された時点で初めてnameの値を見て結果を出しています。

実際に上の続きでnameを削除すると

1
2
3
4
5
6
7
8
>>> del name
>>> a['myName'].execute()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in execute
  File "<stdin>", line 2, in <lambda>
NameError: global name 'name' is not defined
>>>

の様にnameが見つけられなくてエラーを出します。

この様に関数に引数を渡したい場合には、 lambda式の変数にデフォルト引数として値を入れてあげると実現できます。

1
2
3
4
5
6
7
8
9
10
11
>>> a = {}
>>> for name in ['myName', 'yourName']:
...     a[name] = CallbackExecutor(lambda x, y=name: echo(x, y))
...     a[name].execute()
...
myName: execute
yourName: execute
>>> a['myName'].execute()
myName: execute
>>> a['yourName'].execute()
yourName: execute

こんな感じ。

ここではyのデフォルト値は式を作った時点でのnameになるので、 ループが終わってnameの値が変更されても myNameの物はmyNameとして残っています。

Sponsored Links
Sponsored Links

« Font Awesome 4.5.0リリース vim-markdownのアップデート »

}