CUBE SUGAR CONTAINER

技術系のこと書きます。

Python: functools.partial() で関数やメソッドを部分適用する

これまで存在すら知らなかったんだけど、標準ライブラリの functools.partial() はなかなか面白く使えそう。 これを使うと関数やメソッドの引数の一部をある値に固定した形で新しい呼び出し可能オブジェクトを作ることができる。

最初の例として functools.partial() の動作確認に使う関数を定義しておこう。 この関数 add() は単にふたつの引数を足し算するもの。

>>> def add(x, y):
...     return x + y
... 

それでは functools.partial() を使って、先ほど定義した add() 関数のひとつ目の引数 x に 1 を部分適用した新しい関数 add_1() を作ってみよう。 ちなみにキーワード引数も使えるので、もし y を部分適用したい場合には functools.partial(add, y=1) になる。

>>> import functools
>>> add_1 = functools.partial(add, 1)

functools.partial() によって作られた新しい関数 add_1() はひとつの引数だけで呼び出すことができる。

>>> add_1(1)
2
>>> add_1(2)
3

とはいえ、上記の例を見ても「使い方は分かったけど使い所はさっぱり分からない」という感じだとおもう。 次はもっと実用的な例を挙げてみよう。

次は itertools.count() を例にしてみよう。 この関数は整数をインクリメントしながら順番に返すイテレータを返す。 イテレータは next() 関数に渡すことで次の要素が得られる。 毎回 next() 関数を使わなきゃいけないのは割りとめんどくさいね。

>>> import itertools
>>> g = itertools.count()
>>> next(g)
0
>>> next(g)
1
>>> next(g)
2

では、functools.partial() を使って next() 関数の引数にイテレータを部分適用してやろう。 これで、引数なしの呼び出しでイテレータの内容を返す呼び出し可能オブジェクトが得られる。

>>> callable = functools.partial(next, g)
>>> callable()
3
>>> callable()
4
>>> callable()
5

このように、引数のいらない呼び出し可能オブジェクトに加工しておくと扱いやすくなる場面も多い。 例えば組み込み関数 iter() にこれを渡した上で、sentinel (番兵) の要素を指定しておけば、その値が得られるまでループするイテレータが得られる。 元々は無限ループするイテレータを、ある特定の値が得られると止まるイテレータにラップできるということ。

>>> ite = iter(callable, 10)
>>> ite
<callable-iterator object at 0x10a4f6a90>
>>> list(ite)
[6, 7, 8, 9]

特に iter() と組み合わせて使うイディオムは強力そうだ。