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)   # キャッシュ済みの結果がないため、11 回再帰呼び出しする
3628800
>>> factorial(5)    # 新たな呼び出しをせず、キャッシュ済みの結果を返す
120
>>> factorial(12)   # 2 回新たに再帰呼び出しする。factorial(10) はキャッシュ済み
479001600

キャッシュはスレッドセーフなので、ラップされた関数を複数のスレッドで使用できます。 これは、同時に更新が行われても、内部のデータ構造の整合性が保たれるという意味です。

最初の呼び出しが完了して結果がキャッシュされるまでに別のスレッドから呼び出された場合、ラップされた関数が複数回呼び出される可能性があります。

関数の呼び出し中はロックを保持しないため、呼び出しが 1 回だけになる保証はありません。 最初の呼び出しがまだ実行中でも、同じ引数で別の呼び出しが行われる可能性があります。

Added in version 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 と少し異なります。 通常のプロパティでは、セッターが定義されていない限り属性への書き込みができません。 これに対して cached_property は書き込みを許可します。

cached_property デコレータが実行されるのは、属性が検索され、かつ同名の属性がまだ存在しない場合だけです。 実行されると、cached_property は同名の属性に値を書き込みます。 その後の属性の読み書きは cached_property メソッドより優先されるため、通常の属性と同じように動作します。

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

cached_property は、複数スレッドで使用する際に起こり得る競合状態を防ぎません。 同じインスタンスに対してゲッター関数が複数回実行され、最後の実行がキャッシュの値を設定する場合があります。 キャッシュされるプロパティが冪等であるなど、同じインスタンスで複数回実行しても害がなければ問題ありません。 同期が必要な場合は、デコレートしたゲッター関数内か、キャッシュされるプロパティにアクセスする箇所の前後で、必要なロックを実装してください。

このデコレータは PEP 412 のキー共有辞書の動作を妨げる点に注意してください。 そのため、インスタンスの辞書が通常より多くのメモリを使うことがあります。

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

ミュータブルなマッピングを利用できない場合や、キーを共有してメモリを節約したい場合は、@lru_cache の上に @property を重ねることでも @cached_property に似た効果を得られます。 @cached_property との違いについては メソッド呼び出しをキャッシュするには どうしたらいいですか? を参照してください。

Added in version 3.8.

バージョン 3.12 で変更: Python 3.12 より前の @cached_property にはドキュメントに記載されていないロックがあり、複数スレッドで使用してもゲッター関数がインスタンスごとに 1 回だけ実行されるよう保証していました。 しかし、このロックはインスタンス単位ではなくプロパティ単位だったため、ロックの競合が許容できないほど多くなる場合がありました。 Python 3.12 以降では、このロックは削除されています。

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))  # ロケールに応じた順序でソート

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

Added in version 3.2.

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

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

キャッシュはスレッドセーフなので、ラップされた関数を複数のスレッドで使用できます。 これは、同時に更新が行われても、内部のデータ構造の整合性が保たれるという意味です。

最初の呼び出しが完了して結果がキャッシュされるまでに別のスレッドから呼び出された場合、ラップされた関数が複数回呼び出される可能性があります。

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

引数のパターンが異なると、別の呼び出しとして扱われ、個別のキャッシュエントリーになることがあります。 例えば、f(a=1, b=2) と f(b=2, a=1) はキーワード引数の順序が異なるため、2 つの別々のキャッシュエントリーになる可能性があります。

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

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

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

typed を真にすると、型の異なる関数の引数は別々にキャッシュされます。 typed が偽の場合、通常は同等の呼び出しとして扱われ、結果は 1 つだけキャッシュされます。 (str や int など、typed が偽でも別々にキャッシュされる型もあります。)

型の区別は関数に直接渡す引数にだけ適用され、その内容には適用されない点に注意してください。 スカラーの引数 Decimal(42) と Fraction(42) は、結果の異なる別々の呼び出しとして扱われます。 一方、タプルの引数 ('answer', Decimal(42)) と ('answer', Fraction(42)) は同等として扱われます。

ラップされた関数には、maxsize と typed の値を示す新しい dict を返す cache_parameters() 関数が追加されます。 これは情報を得るためだけのもので、値を変更しても効果はありません。

キャッシュの効果の測定や maxsize 引数の調整に役立つように、ラップされた関数には cache_info() 関数が追加されます。 この関数は hits、misses、maxsize、currsize を示す 名前付きタプル を返します。

このデコレータはキャッシュを消去または無効化するための cache_clear() 関数も提供します。

ラップされた元の関数には __wrapped__ 属性でアクセスできます。 これは内観やキャッシュの回避、別のキャッシュによる関数のラップし直しに便利です。

引数と戻り値への参照は、古くなってキャッシュから削除されるか、キャッシュが消去されるまで保持されます。

メソッドをキャッシュする場合、インスタンスの引数 self もキャッシュに含まれます。 メソッド呼び出しをキャッシュするには どうしたらいいですか? を参照してください。

LRU (least recently used) キャッシュ は、最近の呼び出しから今後の呼び出しをよく予測できる場合に最も効果を発揮します (例えば、ニュースサーバーで人気の記事は日ごとに変わる傾向があります)。 キャッシュのサイズ制限により、ウェブサーバーなど長時間実行するプロセスでもキャッシュが際限なく増えることを防ぎます。

一般に LRU キャッシュは、以前計算した値を再利用したい場合にのみ使ってください。 したがって、副作用のある関数、呼び出すたびに別のミュータブルなオブジェクトを作る必要のある関数 (ジェネレータや非同期関数など)、time() や random() のような純粋でない関数をキャッシュしても意味がありません。

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

@lru_cache(maxsize=32)
def get_pep(num):
    '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)

Added in version 3.2.

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

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

バージョン 3.9 で変更: cache_parameters() 関数が追加されました。

@functools.total_ordering

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

クラスには __lt__()、__le__()、__gt__()、__ge__() のいずれかを定義する必要があります。 加えて __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()))

注釈

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

注釈

このデコレータは、クラス またはそのスーパークラス で宣言されているメソッドをオーバーライドしません。 つまり、スーパークラスが比較演算子を定義している場合は、元のメソッドが抽象メソッドであっても total_ordering はそれを再実装しません。

Added in version 3.2.

バージョン 3.4 で変更: 認識できない型に対して、元の比較関数が NotImplemented を返すことをサポートしました。

functools.Placeholder

partial() や partialmethod() の呼び出し時に、位置引数の場所を確保するための番兵として使うシングルトンオブジェクトです。

Added in version 3.14.

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

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

def partial(func, /, *args, **keywords):
    def newfunc(*more_args, **more_keywords):
        return func(*args, *more_args, **(keywords | more_keywords))
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

partial() 関数は関数の部分適用に使います。 関数の位置引数やキーワード引数の一部を「固定」し、より単純なシグネチャの新しいオブジェクトを作ります。 例えば partial() を使うと、base 引数のデフォルトが 2 である点を除いて int() 関数と同じように動作する、呼び出し可能オブジェクトを作れます。

>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = '2 進数の文字列を int に変換する。'
>>> basetwo('10010')
18

args に Placeholder の番兵が含まれている場合、partial() の呼び出し時にはその位置が先に埋められます。 これにより、partial() の呼び出しで任意の位置引数をあらかじめ埋められます。 Placeholder がなければ、先頭から指定した個数の位置引数しか埋められません。

Placeholder の番兵が含まれる場合、呼び出し時にすべてを埋める必要があります。

>>> say_to_world = partial(print, Placeholder, Placeholder, "world!")
>>> say_to_world('Hello', 'dear')
Hello dear world!

say_to_world('Hello') を呼び出すと TypeError を送出します。 埋めるべきプレースホルダーが 2 つあるのに、位置引数が 1 つしか渡されていないためです。

既存の partial オブジェクト に partial() を適用すると、入力オブジェクトの Placeholder の番兵が新しい位置引数で埋められます。 元の Placeholder の位置に新しい Placeholder の番兵を入れると、プレースホルダーを維持できます。

>>> from functools import partial, Placeholder as _
>>> remove = partial(str.replace, _, _, '')
>>> message = 'Hello, dear dear world!'
>>> remove(message, ' dear')
'Hello, world!'
>>> remove_dear = partial(remove, _, ' dear')
>>> remove_dear(message)
'Hello, world!'
>>> remove_first_dear = partial(remove_dear, _, 1)
>>> remove_first_dear(message)
'Hello, dear world!'

Placeholder をキーワード引数として partial() に渡すことはできません。

バージョン 3.14 で変更: 位置引数での Placeholder のサポートが追加されました。

class functools.partialmethod(func, /, *args, **keywords)

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

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

func がデスクリプタ (通常の Python 関数、classmethod()、staticmethod()、abstractmethod()、別の partialmethod インスタンスなど) の場合、__get__ の呼び出しは元のデスクリプタに委譲され、適切な partial オブジェクト が返されます。

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

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

>>> 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

Added in version 3.4.

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

2 つの引数を取る function を、iterable の要素に左から右へ累積的に適用し、単一の値にまとめます。 例えば reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) は ((((1+2)+3)+4)+5) を計算します。 左の引数 x は累積した値、右の引数 y は iterable から得た、更新に使う値です。 省略可能な initial を指定すると、計算時に iterable の要素より前に置かれ、iterable が空の場合のデフォルト値にもなります。 initial を指定せず、iterable に要素が 1 つしかない場合は、その要素を返します。

およそ次と等価です:

initial_missing = sentinel('initial_missing')

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() を参照してください。

バージョン 3.14 で変更: initial をキーワード引数として指定できるようになりました。

@functools.singledispatch

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

ジェネリック関数を定義するには、@singledispatch デコレータを付けます。 @singledispatch で関数を定義する場合、ディスパッチは最初の引数の型に基づく点に注意してください。

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

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

>>> @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)

typing.Union も使えます。

>>> @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)
...

コレクションの型 (例: list) に基づいてディスパッチし、その要素には型ヒント (例: list[int]) を付けたい場合は、ディスパッチに使う型をデコレータに明示的に渡し、型ヒントは関数定義に記述してください。

>>> @fun.register(list)
... def _(arg: list[int], verbose=False):
...     if verbose:
...         print("Enumerate this:")
...     for i, elem in enumerate(arg):
...         print(i, elem)

注釈

実行時には、リストに含まれる要素の型に関係なく、リストのインスタンスに対して同じ実装を選びます。 つまり、[1,2,3] と ["foo", "bar", "baz"] は同じようにディスパッチされます。 この例のアノテーションは静的型チェッカーのためだけのもので、実行時には影響しません。

ラムダ式 や既存の関数を登録できるように、register() 属性は関数として呼び出すこともできます。

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

register() 属性は、デコレートする前の関数を返します。 これにより、デコレータを重ねたり、pickle 化 したり、実装ごとに独立した単体テストを作成したりできます。

>>> @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>

Added in version 3.4.

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

バージョン 3.11 で変更: register() 属性が型アノテーションとして typing.Union をサポートしました。

class functools.singledispatchmethod(func)

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

ジェネリックメソッドを定義するには、@singledispatchmethod デコレータを付けます。 @singledispatchmethod でメソッドを定義する場合、self と cls を除いた最初の引数の型に基づいてディスパッチされる点に注意してください。

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、@~abc.abstractmethod など、同様のデコレータにもこの方法を使えます。

Added in version 3.8.

バージョン 3.15 で変更: descriptor ではない呼び出し可能オブジェクトのサポートが追加されました。

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

wrapper 関数を更新し、wrapped 関数と同じ属性を持つようにします。 省略可能な引数はタプルで、元の関数のどの属性をラッパー関数の同名の属性に直接代入するか、またラッパー関数のどの属性を元の関数の同名の属性で更新するかを指定します。 これらの引数のデフォルト値はモジュールレベルの定数 WRAPPER_ASSIGNMENTS と WRAPPER_UPDATES です。 前者はラッパー関数の __module__、__name__、__qualname__、__annotations__、__type_params__、およびドキュメンテーション文字列 __doc__ に代入し、後者は __dict__ (インスタンスの辞書) を更新します。

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

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

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

バージョン 3.2 で変更: __wrapped__ 属性が自動的に追加されるようになりました。 __annotations__ 属性がデフォルトでコピーされるようになりました。 属性が欠けていても AttributeError を送出しなくなりました。

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

バージョン 3.12 で変更: __type_params__ 属性がデフォルトでコピーされるようになりました。

@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 オブジェクトは、呼び出し可能で、弱参照でき、属性を持てるという点で 関数オブジェクト に似ています。 ただし重要な違いもあります。 例えば __name__ と __doc__ 属性は自動的には作成されません。