functools --- 高階関数と呼び出し可能オブジェクトの操作

ソースコード: Lib/functools.py


functools モジュールは高階関数、つまり関数に影響を及ぼしたり他の関数を返したりする関数のためのものです。一般に、どんな呼び出し可能オブジェクトでもこのモジュールの目的には関数として扱えます。

モジュール functools は以下の関数を定義します:

@functools.cache(user_function)

簡単で軽量な無制限の関数キャッシュです。 "メモ化 (memoize)" とも呼ばれることがあります。

lru_cache(maxsize=None) と同じ関数を返し、関数の引数に対するルックアップ辞書を含む薄いラッパーを生成します。キャッシュ上の古い値を追い出す必要がないため、キャッシュサイズに制限のある lru_cache() よりも軽量で高速です。

例えば:

@cache
def factorial(n):
    return n * factorial(n-1) if n else 1

>>> factorial(10)      # no previously cached result, makes 11 recursive calls
3628800
>>> factorial(5)       # just looks up cached value result
120
>>> factorial(12)      # makes two new recursive calls, the other 10 are cached
479001600

The cache is threadsafe so that the wrapped function can be used in multiple threads. This means that the underlying data structure will remain coherent during concurrent updates.

It is possible for the wrapped function to be called more than once if another thread makes an additional call before the initial call has been completed and cached.

バージョン 3.9 で追加.

@functools.cached_property(func)

クラスのメソッドを、値を一度だけ計算して通常の属性としてキャッシュするプロパティに変換します。キャッシュはインスタンスの生存期間にわたって有効です。property() に似ていて、さらにキャッシュを行います。計算コストが高く、一度計算すればその後は不変であるようなインスタンスのプロパティに対して有用です。

以下はプログラム例です:

class DataSet:

    def __init__(self, sequence_of_numbers):
        self._data = tuple(sequence_of_numbers)

    @cached_property
    def stdev(self):
        return statistics.stdev(self._data)

cached_property() のしくみは property() とやや異なります。通常のプロパティは、セッター (setter) が定義されない限り書き込みを禁止します。対照的に、 cached_property は書き込みを許します。

cached_property デコレータはルックアップテーブルで、同名の属性が存在しない場合のみ動作します。動作した場合、 cached_property は同名の属性に書き込みを行います。その後の属性の読み込みと書き込みは cached_property メソッドより優先され、通常の属性のように働きます。

キャッシュされた値は属性を削除することで取り除くことができます。これにより cached_property メソッドを再度実行することが可能になります。

The cached_property does not prevent a possible race condition in multi-threaded usage. The getter function could run more than once on the same instance, with the latest run setting the cached value. If the cached property is idempotent or otherwise not harmful to run more than once on an instance, this is fine. If synchronization is needed, implement the necessary locking inside the decorated getter function or around the cached property access.

このデコレータは PEP 412 のキー共有辞書のインターフェースを持ちます。これは、インスタンス辞書がより多くのスペースを使う可能性があることを意味します。

また、このデコレータは各インスタンスの __dict__ 属性が可変のマッピングであることを要求します。すなわち、このデコレータはいくつかの型、たとえばメタクラス (型インスタンスの __dict__ 属性はクラスの名前空間に対する読み込み専用のプロキシであるため) や、 __slots__ を指定していてその中に __dict__ を含まない型 (それ自体が __dict__ 属性を提供しないため) に対しては動作しないことを意味します。

If a mutable mapping is not available or if space-efficient key sharing is desired, an effect similar to cached_property() can also be achieved by stacking property() on top of lru_cache(). See メソッド呼び出しをキャッシュするには どうしたらいいですか? for more details on how this differs from cached_property().

バージョン 3.8 で追加.

バージョン 3.12 で変更: Prior to Python 3.12, cached_property included an undocumented lock to ensure that in multi-threaded usage the getter function was guaranteed to run only once per instance. However, the lock was per-property, not per-instance, which could result in unacceptably high lock contention. In Python 3.12+ this locking is removed.

functools.cmp_to_key(func)

古いスタイルの比較関数を key function に変換します。key 関数を受け取るツール (sorted(), min(), max(), heapq.nlargest(), heapq.nsmallest(), itertools.groupby() など) と共に使用します。この関数は、主に比較関数を使っていた Python 2 からプログラムの移行のための変換ツールとして使われます。

比較関数は2つの引数を受け取り、それらを比較し、 "より小さい" 場合は負の数を、同値の場合には 0 を、 "より大きい" 場合には正の数を返す、あらゆる呼び出し可能オブジェクトです。key 関数は呼び出し可能オブジェクトで、1つの引数を受け取り、ソートキーとして使われる値を返します。

以下はプログラム例です:

sorted(iterable, key=cmp_to_key(locale.strcoll))  # locale-aware sort order

ソートの例と簡単なチュートリアルは Sorting Techniques を参照して下さい。

バージョン 3.2 で追加.

@functools.lru_cache(user_function)
@functools.lru_cache(maxsize=128, typed=False)

関数をメモ化用の呼び出し可能オブジェクトでラップし、最近の呼び出し最大 maxsize 回まで保存するするデコレータです。高価な関数や I/O に束縛されている関数を定期的に同じ引数で呼び出すときに、時間を節約できます。

The cache is threadsafe so that the wrapped function can be used in multiple threads. This means that the underlying data structure will remain coherent during concurrent updates.

It is possible for the wrapped function to be called more than once if another thread makes an additional call before the initial call has been completed and cached.

結果のキャッシュには辞書が使われるので、関数の位置引数およびキーワード引数は ハッシュ可能 でなくてはなりません。

引数のパターンが異なる場合は、異なる呼び出しと見なされ別々のキャッシュエントリーとなります。 例えば、 f(a=1, b=2)f(b=2, a=1) はキーワード引数の順序が異なっているので、2つの別個のキャッシュエントリーになります。

user_function が指定された場合、それは呼び出し可能でなければなりません。これにより lru_cache デコレータがユーザー関数に直接適用できるようになります。このとき maxsize の値はデフォルトの 128 となります:

@lru_cache
def count_vowels(sentence):
    return sum(sentence.count(vowel) for vowel in 'AEIOUaeiou')

maxsizeNone に設定された場合は、LRU 機能は無効化され、キャッシュは際限無く大きくなります。

If typed is set to true, function arguments of different types will be cached separately. If typed is false, the implementation will usually regard them as equivalent calls and only cache a single result. (Some types such as str and int may be cached separately even when typed is false.)

Note, type specificity applies only to the function's immediate arguments rather than their contents. The scalar arguments, Decimal(42) and Fraction(42) are be treated as distinct calls with distinct results. In contrast, the tuple arguments ('answer', Decimal(42)) and ('answer', Fraction(42)) are treated as equivalent.

The wrapped function is instrumented with a cache_parameters() function that returns a new dict showing the values for maxsize and typed. This is for information purposes only. Mutating the values has no effect.

キャッシュ効率の測定や maxsize パラメータの調整をしやすくするため、ラップされた関数には cache_info() 関数が追加されます。この関数は hits, misses, maxsize, currsize を示す named tuple を返します。

このデコレータは、キャッシュの削除と無効化のための cache_clear() 関数も提供します。

元々の基底の関数には、 __wrapped__ 属性を通してアクセスできます。これはキャッシュを回避して、または関数を別のキャッシュでラップして、内観するのに便利です。

The cache keeps references to the arguments and return values until they age out of the cache or until the cache is cleared.

If a method is cached, the self instance argument is included in the cache. See メソッド呼び出しをキャッシュするには どうしたらいいですか?

LRU (least recently used) キャッシュ は、最も新しい呼び出しが次の呼び出しで最も現れやすいとき (例えば、最もニュースサーバの人気の記事が日ごとに変わる傾向にある場合) に最も最も効率よくはたらきます。キャッシュのサイズ制限は、キャッシュがウェブサーバの長期間に渡るプロセスにおける限界を超えては大きくならないことを保証します。

In general, the LRU cache should only be used when you want to reuse previously computed values. Accordingly, it doesn't make sense to cache functions with side-effects, functions that need to create distinct mutable objects on each call (such as generators and async functions), or impure functions such as time() or random().

静的 web コンテンツ の LRU キャッシュの例:

@lru_cache(maxsize=32)
def get_pep(num):
    'Retrieve text of a Python Enhancement Proposal'
    resource = f'https://peps.python.org/pep-{num:04d}'
    try:
        with urllib.request.urlopen(resource) as s:
            return s.read()
    except urllib.error.HTTPError:
        return 'Not Found'

>>> for n in 8, 290, 308, 320, 8, 218, 320, 279, 289, 320, 9991:
...     pep = get_pep(n)
...     print(n, len(pep))

>>> get_pep.cache_info()
CacheInfo(hits=3, misses=8, maxsize=32, currsize=8)

キャッシュを使って 動的計画法 の技法を実装し、フィボナッチ数 を効率よく計算する例:

@lru_cache(maxsize=None)
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

>>> [fib(n) for n in range(16)]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]

>>> fib.cache_info()
CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)

バージョン 3.2 で追加.

バージョン 3.3 で変更: typed オプションが追加されました。

バージョン 3.8 で変更: user_function オプションが追加されました。

バージョン 3.9 で変更: Added the function cache_parameters()

@functools.total_ordering

ひとつ以上の拡張順序比較メソッド (rich comparison ordering methods) を定義したクラスを受け取り、残りを実装するクラスデコレータです。このデコレータは全ての拡張順序比較演算をサポートするための労力を軽減します:

引数のクラスは、 __lt__(), __le__(), __gt__(), __ge__() の中からどれか1つと、 __eq__() メソッドを定義する必要があります。

例えば:

@total_ordering
class Student:
    def _is_valid_operand(self, other):
        return (hasattr(other, "lastname") and
                hasattr(other, "firstname"))
    def __eq__(self, other):
        if not self._is_valid_operand(other):
            return NotImplemented
        return ((self.lastname.lower(), self.firstname.lower()) ==
                (other.lastname.lower(), other.firstname.lower()))
    def __lt__(self, other):
        if not self._is_valid_operand(other):
            return NotImplemented
        return ((self.lastname.lower(), self.firstname.lower()) <
                (other.lastname.lower(), other.firstname.lower()))

注釈

このデコレータにより、完全に順序の付いた振る舞いの良い型を簡単に作ることができますが、実行速度は遅くなり、派生した比較メソッドのスタックトレースは複雑になります。性能ベンチマークにより、これがアプリケーションのボトルネックになっていることがわかった場合は、代わりに 6 つの拡張比較メソッドをすべて実装すれば、簡単にスピードアップを図れるでしょう。

注釈

This decorator makes no attempt to override methods that have been declared in the class or its superclasses. Meaning that if a superclass defines a comparison operator, total_ordering will not implement it again, even if the original method is abstract.

バージョン 3.2 で追加.

バージョン 3.4 で変更: 認識できない型に対して下層の比較関数から NotImplemented を返すことがサポートされるようになりました。

functools.partial(func, /, *args, **keywords)

新しい partial オブジェクト を返します。このオブジェクトは呼び出されると位置引数 args とキーワード引数 keywords 付きで呼び出された func のように振る舞います。呼び出しに際してさらなる引数が渡された場合、それらは args に付け加えられます。追加のキーワード引数が渡された場合には、それらで keywords を拡張または上書きします。おおよそ次のコードと等価です:

def partial(func, /, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = {**keywords, **fkeywords}
        return func(*args, *fargs, **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

関数 partial() は、関数の位置引数・キーワード引数の一部を「凍結」した部分適用として使われ、簡素化された引数形式をもった新たなオブジェクトを作り出します。例えば、 partial() を使って base 引数のデフォルトが 2 である int() 関数のように振る舞う呼び出し可能オブジェクトを作ることができます:

>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18
class functools.partialmethod(func, /, *args, **keywords)

partial と似た動作をする新しい partialmethod 記述子 (デスクリプタ) を返します。直接呼び出しではなく、メソッド定義としての使用が目的であることのみが、partial とは異なります。

func は、descriptor または呼び出し可能オブジェクトである必要があります (通常の関数など、両方の性質を持つオブジェクトは記述子として扱われます。)

func が記述子 (Python の通常の関数、 classmethod()staticmethod()abstractmethod() または別の partialmethod のインスタンスなど) の場合、 __get__ への呼び出しは下層の記述子に委譲され、返り値として適切な partial オブジェクト が返されます。

func が記述子以外の呼び出し可能オブジェクトである場合、適切な束縛メソッドが動的に作成されます。この func は、メソッドとして使用された場合、Python の通常の関数と同様に動作します。 partialmethod コンストラクタに argskeywords が渡されるよりも前に、 self 引数が最初の位置引数として挿入されます。

以下はプログラム例です:

>>> 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.alive
False
>>> c.set_alive()
>>> c.alive
True

バージョン 3.4 で追加.

functools.reduce(function, iterable, [initial, ]/)

Apply function of two arguments cumulatively to the items of iterable, from left to right, so as to reduce the iterable to a single value. For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates ((((1+2)+3)+4)+5). The left argument, x, is the accumulated value and the right argument, y, is the update value from the iterable. If the optional initial is present, it is placed before the items of the iterable in the calculation, and serves as a default when the iterable is empty. If initial is not given and iterable contains only one item, the first item is returned.

およそ次と等価です:

initial_missing = object()

def reduce(function, iterable, initial=initial_missing, /):
    it = iter(iterable)
    if initial is initial_missing:
        value = next(it)
    else:
        value = initial
    for element in it:
        value = function(value, element)
    return value

全ての中間値を返すイテレータについては itertools.accumulate() を参照してください。

@functools.singledispatch

関数を シングルディスパッチ ジェネリック関数 に変換します。

To define a generic function, decorate it with the @singledispatch decorator. When defining a function using @singledispatch, note that the dispatch happens on the type of the first argument:

>>> from functools import singledispatch
>>> @singledispatch
... def fun(arg, verbose=False):
...     if verbose:
...         print("Let me just say,", end=" ")
...     print(arg)

関数にオーバーロード実装を追加するには、デコレータとして使用できる、ジェネリック関数の register() 属性を使用します。 型アノテーションが付いている関数については、このデコレータは1つ目の引数の型を自動的に推測します。

>>> @fun.register
... def _(arg: int, verbose=False):
...     if verbose:
...         print("Strength in numbers, eh?", end=" ")
...     print(arg)
...
>>> @fun.register
... def _(arg: list, verbose=False):
...     if verbose:
...         print("Enumerate this:")
...     for i, elem in enumerate(arg):
...         print(i, elem)

types.UnionType and typing.Union can also be used:

>>> @fun.register
... def _(arg: int | float, verbose=False):
...     if verbose:
...         print("Strength in numbers, eh?", end=" ")
...     print(arg)
...
>>> from typing import Union
>>> @fun.register
... def _(arg: Union[list, set], verbose=False):
...     if verbose:
...         print("Enumerate this:")
...     for i, elem in enumerate(arg):
...         print(i, elem)
...

型アノテーションを使っていないコードについては、デコレータに適切な型引数を明示的に渡せます:

>>> @fun.register(complex)
... def _(arg, verbose=False):
...     if verbose:
...         print("Better than complicated.", end=" ")
...     print(arg.real, arg.imag)
...

register() 属性を関数形式で使用すると、lambda 関数と既存の関数の登録を有効にできます:

>>> def nothing(arg, verbose=False):
...     print("Nothing.")
...
>>> fun.register(type(None), nothing)

The register() attribute returns the undecorated function. This enables decorator stacking, pickling, and the creation of unit tests for each variant independently:

>>> @fun.register(float)
... @fun.register(Decimal)
... def fun_num(arg, verbose=False):
...     if verbose:
...         print("Half of your number:", end=" ")
...     print(arg / 2)
...
>>> fun_num is fun
False

汎用関数は、呼び出されると 1 つ目の引数の型でディスパッチします:

>>> fun("Hello, world.")
Hello, world.
>>> fun("test.", verbose=True)
Let me just say, test.
>>> fun(42, verbose=True)
Strength in numbers, eh? 42
>>> fun(['spam', 'spam', 'eggs', 'spam'], verbose=True)
Enumerate this:
0 spam
1 spam
2 eggs
3 spam
>>> fun(None)
Nothing.
>>> fun(1.23)
0.615

特定の型について登録された実装が存在しない場合、その型のメソッド解決順序が、汎用の実装をさらに検索するために使用されます。@singledispatch でデコレートされた元の関数は基底の object 型に登録されます。これは、他によりよい実装が見つからないことを意味します。

抽象基底クラス (abstract base class) に対して実装が登録されると、基底クラスの仮想サブクラスに対してもその実装がディスパッチされます:

>>> from collections.abc import Mapping
>>> @fun.register
... def _(arg: Mapping, verbose=False):
...     if verbose:
...         print("Keys & Values")
...     for key, value in arg.items():
...         print(key, "=>", value)
...
>>> fun({"a": "b"})
a => b

指定された型に対して、汎用関数がどの実装を選択するかを確認するには、dispatch() 属性を使用します:

>>> fun.dispatch(float)
<function fun_num at 0x1035a2840>
>>> fun.dispatch(dict)    # note: default implementation
<function fun at 0x103fe0000>

登録されたすべての実装にアクセスするには、読み出し専用の registry 属性を使用します:

>>> fun.registry.keys()
dict_keys([<class 'NoneType'>, <class 'int'>, <class 'object'>,
          <class 'decimal.Decimal'>, <class 'list'>,
          <class 'float'>])
>>> fun.registry[float]
<function fun_num at 0x1035a2840>
>>> fun.registry[object]
<function fun at 0x103fe0000>

バージョン 3.4 で追加.

バージョン 3.7 で変更: register() 属性が型アノテーションの使用をサポートするようになりました。

バージョン 3.11 で変更: The register() attribute now supports types.UnionType and typing.Union as type annotations.

class functools.singledispatchmethod(func)

メソッドを シングルディスパッチ ジェネリック関数 に変換します。

To define a generic method, decorate it with the @singledispatchmethod decorator. When defining a function using @singledispatchmethod, note that the dispatch happens on the type of the first non-self or non-cls argument:

class Negator:
    @singledispatchmethod
    def neg(self, arg):
        raise NotImplementedError("Cannot negate a")

    @neg.register
    def _(self, arg: int):
        return -arg

    @neg.register
    def _(self, arg: bool):
        return not arg

@singledispatchmethod@classmethod など他のデコレータとの入れ子構造をサポートします。 dispatcher.register を可能にするためには、 singledispatchmethod は入れ子構造の中で 最も外側 のデコレータでなければなりません。この Negator クラスの例では、クラスのインスタンスにではなく、クラスに neg メソッドが紐付きます:

class Negator:
    @singledispatchmethod
    @classmethod
    def neg(cls, arg):
        raise NotImplementedError("Cannot negate a")

    @neg.register
    @classmethod
    def _(cls, arg: int):
        return -arg

    @neg.register
    @classmethod
    def _(cls, arg: bool):
        return not arg

同様のパターンが他の似たようなデコレータに対しても適用できます: @staticmethod, @abstractmethod など。

バージョン 3.8 で追加.

functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)

wrapper 関数を wrapped 関数に見えるようにアップデートします。オプション引数はタプルで、元の関数のどの属性がラッパー関数の対応する属性に直接代入されるか、またラッパー関数のどの属性が元の関数の対応する属性でアップデートされるか、を指定します。これらの引数のデフォルト値は、モジュールレベル定数 WRAPPER_ASSIGNMENTS (これはラッパー関数の __module__, __name__, __qualname__, __annotations__ そしてドキュメンテーション文字列 __doc__ に代入する) と WRAPPER_UPDATES (これはラッパー関数の __dict__ すなわちインスタンス辞書をアップデートする) です。

内観や別の目的 (例えば、 lru_cache() のようなキャッシュするデコレータの回避) のために元の関数にアクセスできるように、この関数はラップされている関数を参照するラッパーに自動的に __wrapped__ 属性を追加します。

この関数は主に関数を包んでラッパーを返す デコレータ 関数の中で使われるよう意図されています。もしラッパー関数がアップデートされないとすると、返される関数のメタデータは元の関数の定義ではなくラッパー関数の定義を反映してしまい、これは通常あまり有益ではありません。

update_wrapper() は、関数以外の呼び出し可能オブジェクトにも使えます。 assigned または updated で指名され、ラップされるオブジェクトに存在しない属性は、すべて無視されます (すなわち、ラッパー関数にそれらの属性を設定しようとは試みられません)。しかし、 updated で指名された属性がラッパー関数自身に存在しないなら AttributeError が送出されます。

バージョン 3.2 で追加: __wrapped__ 属性の自動的な追加。

バージョン 3.2 で追加: デフォルトで __annotations__ 属性がコピーされます。

バージョン 3.2 で変更: 存在しない属性によって AttributeError を発生しなくなりました。

バージョン 3.4 で変更: ラップされた関数が __wrapped__ を定義していない場合でも、 __wrapped__ が常にラップされた関数を参照するようになりました。(bpo-17482 を参照)

@functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)

これはラッパー関数を定義するときに update_wrapper() を関数デコレータとして呼び出す便宜関数です。これは partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated) と等価です。例えば:

>>> from functools import wraps
>>> def my_decorator(f):
...     @wraps(f)
...     def wrapper(*args, **kwds):
...         print('Calling decorated function')
...         return f(*args, **kwds)
...     return wrapper
...
>>> @my_decorator
... def example():
...     """Docstring"""
...     print('Called example function')
...
>>> example()
Calling decorated function
Called example function
>>> example.__name__
'example'
>>> example.__doc__
'Docstring'

このデコレータ・ファクトリを使用しないと、上の例中の関数の名前は 'wrapper' となり、元の example() のドキュメンテーション文字列は失われてしまいます。

partial オブジェクト

partial オブジェクトは、 partial() 関数によって作られる呼び出し可能オブジェクトです。オブジェクトには読み出し専用の属性が三つあります:

partial.func

呼び出し可能オブジェクトまたは関数です。 partial オブジェクトの呼び出しは新しい引数とキーワードと共に func に転送されます。

partial.args

最左の位置引数で、 partial オブジェクトの呼び出し時にその呼び出しの際の位置引数の前に追加されます。

partial.keywords

partial オブジェクトの呼び出し時に渡されるキーワード引数です。

partial objects are like function objects in that they are callable, weak referenceable, and can have attributes. There are some important differences. For instance, the __name__ and __doc__ attributes are not created automatically. Also, partial objects defined in classes behave like static methods and do not transform into bound methods during instance attribute look-up.