Python
 Computer >> コンピューター >  >> プログラミング >> Python

Pythonのfunctoolsモジュール入門 — 高階関数と呼び出し可能オブジェクトを自在に操る

Pythonにおいて、関数は「高階関数(higher-order function)」として扱われます。これは、関数そのものを別の関数の引数として渡したり、関数の戻り値として他の関数を返したりできることを意味します。標準ライブラリのfunctoolsモジュールは、こうした高階関数を扱うための便利なユーティリティを多数提供しています。

この記事では、functoolsモジュールの中でも特に実用性の高いpartial()partialmethod()cmp_to_key()reduce()の4つの機能について、具体的なコード例とともに解説します。

partial()関数 — 引数を「固定」した新しい関数を作る

partial()関数は、「部分適用(partial application)」を行うための機能です。既存の関数の一部の引数を固定(凍結)し、シグネチャが簡略化された新しい呼び出し可能オブジェクトを返します。返されたオブジェクト自体も、通常の関数と同じように呼び出すことができます。

int()関数を例にした基本形

組み込みのint()関数は、数値や文字列を10進整数に変換する関数で、デフォルトのシグネチャは次のとおりです。

int(x, base=10)

partial()を使えば、base引数を2に固定した、いわば「2進数専用のint()」を簡単に作れます。

>>> import functools
>>> binint = functools.partial(int, base=2)
>>> binint('1001')
9

文字列'1001'が2進数として解釈され、結果は9になりました。このように、頻繁に使う引数の組み合わせを持つ関数を、簡潔な名前で再利用できるのがpartial()の利点です。

ユーザー定義関数への適用

もちろん、自分で定義した関数にも使えます。次の例では、元の関数の引数bにデフォルト値10を設定した新しい関数を作成しています。

>>> def myfunction(a, b):
...     return a * b
...
>>> partfunction = functools.partial(myfunction, b=10)
>>> partfunction(10)
100

partfunction(10)は内部的にはmyfunction(10, b=10)として実行されるため、結果は100となります。

partialmethod() — クラスのメソッド向けの部分適用

partialmethod()は、partial()とほぼ同じ働きをするディスクリプタを返します。違いは、これが直接呼び出し可能な関数ではなく、クラス内でのメソッド定義に使うよう設計されている点です。クラスのメソッドに対して引数を固定したい場合に活用します。

from functools import partialmethod

class Cell:
    def __init__(self):
        self._alive = False

    @property
    def alive(self):
        return self._alive

    def set_state(self, state):
        self._alive = bool(state)

    set_alive = partialmethod(set_state, True)
    set_dead = partialmethod(set_state, False)

c = Cell()
c.set_alive()
print(c.alive)   # True
c.set_dead()
print(c.alive)   # False

このように、似たようなメソッドを複数定義する代わりに、partialmethod()を使えばコードをすっきりまとめられます。

cmp_to_key() — Python 2スタイルの比較関数を現代風に変換する

Python 2.xには、2つのオブジェクトを比較するためのcmp()組み込み関数がありましたが、Python 3では廃止されました。そこでfunctoolsモジュールが提供しているのがcmp_to_key()です。この関数を使うと、旧来の比較関数(2つの引数を受け取り大小関係を返す関数)を、sorted()などのkey引数で使える形式へ変換できます。

from functools import cmp_to_key

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

    def __str__(self):
        return str(self.x)

def cmpr(a, b):
    if a.x > b.x:
        return 1
    elif a.x < b.x:
        return -1
    else:
        return 0

items = [Test(3), Test(1), Test(2)]
items.sort(key=cmp_to_key(cmpr))
for item in items:
    print(item)   # 1, 2, 3 の順に出力

比較関数は、aが大きければ正の値、小さければ負の値、等しければ0を返すように書くのが一般的です。独自クラスのオブジェクトを柔軟にソートしたい場合に非常に有用です。

reduce()関数 — 反復処理を1つの値に畳み込む

reduce()関数は、関数イテラブルの2つの引数を受け取り、単一の値を返します。指定した関数が、イテラブルの要素に対して左から右へ累積的に適用されていく仕組みです。最初の呼び出しの結果が次の呼び出しの第1引数となり、リストの次の要素が第2引数になります。これを要素が尽きるまで繰り返します。

次の例では、2つの数の積を返すmult()関数を定義し、1から10までの範囲の数値とともにreduce()へ渡しています。結果として得られるのは、10の階乗(10!)です。

import functools

def mult(x, y):
    return x * y

num = functools.reduce(mult, range(1, 11))
print('factorial of 10: ', num)

実行結果

factorial of 10: 3628800

計算の流れを追うと、まずmult(1, 2)が実行され、その結果2が次のmult(2, 3)の第1引数になります。以降も同様に畳み込みが進み、最終的に3628800という値が得られます。

なお、Python 3ではreduce()は組み込み関数ではなくfunctoolsモジュールに移動しているため、使用前にインポートが必要な点に注意してください。

まとめ

functoolsモジュールは、Pythonの「関数は第一級オブジェクト」という性質を最大限に活かすための道具箱です。partial()で引数を固定した派生関数を作り、partialmethod()でメソッド定義を簡潔にし、cmp_to_key()で柔軟な比較ロジックを実装し、reduce()で反復処理を集約できる——これらをマスターすれば、より宣言的で読みやすいPythonコードが書けるようになります。

  1. Pythonの関数名の前後に付く__(ダブルアンダースコア)の意味とは?理由をわかりやすく解説

    Pythonのコードを読んでいると、__init__ や __str__ のように、名前の前後にダブルアンダースコア(二重のアンダースコア __)が付いた関数やメソッドを目にすることがあります。これらは「ダンダー(dunder:double underscore の略)メソッド」あるいは「特殊メソッド」「マジックメソッド」と呼ばれるもので、Pythonの言語仕様において特別な意味を持つ名前です。 ダブルアンダースコアはPython側で予約された名前 前後にダブルアンダースコアが付いた名前は、Pythonの組み込み機能(ビルトイン)として実質的に予約されています。これらはクラスの振る舞いをカス

  2. Pythonで関数から別の関数を返す方法をわかりやすく解説

    Pythonは第一級関数(first-class functions)をサポートしているプログラミング言語です。実際、Pythonにおけるすべての関数は第一級オブジェクトとして扱われます。 これは何を意味するのでしょうか。具体的には、以下のようなことが可能になります。 関数の中から別の関数を返す(returnする) 関数をリストや辞書などのコレクションに格納する 関数を、通常の変数やオブジェクトと同じように扱う さらに、関数の中で別の関数を定義し、それを返すことさえできます。これはクロージャ(closure)と呼ばれる強力なテクニックの基礎にもなる概念です。 サンプルコード:関数内で関数を