2015年12月6日

functools.partial()

引数の部分適用を実現する functools.partial() を紹介します。
部分適用とは、一部引数の値を固定にし、別の関数として扱うことです。


リトルエンディアンで2バイト、4バイト、2バイトの整数が格納されているバイト列を考えます。

>>> import binascii
>>> b = binascii.unhexlify('0100020000000300')

このバイト列から2バイト、4バイト、2バイトの順で3つの整数を取り出すソースコードは以下のようになります。
>>> i1 = int.from_bytes(b[0:2], 'little')
>>> i2 = int.from_bytes(b[2:6], 'little')
>>> i3 = int.from_bytes(b[6:8], 'little')
>>> i1, i2, i3
(1, 2, 3)

上のソースコードでは、int.from_bytes() を呼ぶ度に 引数'little' を指定しているのが、あまり格好良い感じではないですね。省略できればなぁ~、と思えてきます。
ここで、functools.partial() を使って引数の部分適用をすると、ソースコードが一気に見やすくなります。
以下は functools.partial() の具体的な使用例です。
>>> from functools import partial
>>> to_int = partial(int.from_bytes, byteorder='little')

>>> i1 = to_int(b[0:2])
>>> i2 = to_int(b[2:6])
>>> i3 = to_int(b[6:8])
>>> i1, i2, i3
(1, 2, 3)
バイト列から整数への変換部分がスッキリしましたね。


上例は functools.partial() の便利さを示すものですが、functools.partial() のより大きな恩恵として「部分適用した関数を変数に代入可能」が挙げられます。

つまり、、、

>>> def other_func(to_int):
...     print(to_int(b[0:2]))
...     print(to_int(b[2:6]))
...     print(to_int(b[6:8]))
...

# partialの戻り値は変数に代入可能
>>> other_func(partial(int.from_bytes, byteorder='little'))
1
2
3

# partial無しでは変数に代入できない
>>> other_func(int.from_bytes(byteorder='little'))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Required argument 'bytes' (pos 1) not found
上記のように、functools.partial() ならば部分適用した関数を変数(ここでは引数)へ代入可能です。
functools.partial() 無しで実現するには、別途関数を定義する、lambdaを使う、等の方法がありますが、簡潔さと可読性の両観点から、部分適用には functools.partial() を使うのが最適であると言えます。

0 件のコメント:

コメントを投稿