10.2. functools
--- 高階関数と呼び出し可能オブジェクトの操作¶
ソースコード: Lib/functools.py
functools
モジュールは高階関数、つまり関数に影響を及ぼしたり他の関数を返したりする関数のためのものです。一般に、どんな呼び出し可能オブジェクトでもこのモジュールの目的には関数として扱えます。
モジュール functools
は以下の関数を定義します:
-
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
ソートの例と簡単なチュートリアルは ソート HOW TO を参照して下さい。
バージョン 3.2 で追加.
-
@
functools.
lru_cache
(maxsize=128, typed=False)¶ 関数をメモ化用の呼び出し可能オブジェクトでラップし、最近の呼び出し最大 maxsize 回まで保存するするデコレータです。高価な関数や I/O に束縛されている関数を定期的に同じ引数で呼び出すときに、時間を節約できます。
結果のキャッシュには辞書が使われるので、関数の位置引数およびキーワード引数はハッシュ可能でなくてはなりません。
maxsize が
None
に設定された場合は、LRU 機能は無効化され、キャッシュは際限無く大きくなります。 LRU 機能は maxsize が 2 の累乗であるときが最も効率的に動作します。typed が真に設定された場合は、関数の異なる型の引数が別々にキャッシュされます。例えば、
f(3)
とf(3.0)
は別の結果をもたらす別の呼び出しとして扱われます。キャッシュ効率の測定や maxsize パラメータの調整をしやすくするため、ラップされた関数には
cache_info()
関数が追加されます。この関数は hits, misses, maxsize, currsize を示す named tuple を返します。マルチスレッド環境では、hits と misses は概算です。このデコレータは、キャッシュの削除と無効化のための
cache_clear()
関数も提供します。元々の基底の関数には、
__wrapped__
属性を通してアクセスできます。これはキャッシュを回避して、または関数を別のキャッシュでラップして、内観するのに便利です。LRU (least recently used) キャッシュ は、最新の呼び出しが次も呼び出される可能性が最も高い場合 (例えば、ニュースサーバーの最も人気のある記事は、毎日変わる傾向にあります) に最も効率が良くなります。キャッシュのサイズ制限は、キャッシュがウェブサーバーなどの長期間に渡るプロセスにおける限界を超えては大きくならないことを保証します。
静的 web コンテンツ の LRU キャッシュの例:
@lru_cache(maxsize=32) def get_pep(num): 'Retrieve text of a Python Enhancement Proposal' resource = 'http://www.python.org/dev/peps/pep-%04d/' % num 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 オプションが追加されました。
-
@
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 つの拡張比較メソッドをすべて実装すれば、簡単にスピードアップを図れるでしょう。
バージョン 3.2 で追加.
バージョン 3.4 で変更: 認識できない型に対して下層の比較関数から NotImplemented を返すことがサポートされるようになりました。
-
functools.
partial
(func, *args, **keywords)¶ Return a new
partial
object which when called will behave like func called with the positional arguments args and keyword arguments keywords. If more arguments are supplied to the call, they are appended to args. If additional keyword arguments are supplied, they extend and override keywords. Roughly equivalent to:def partial(func, *args, **keywords): def newfunc(*fargs, **fkeywords): newkeywords = keywords.copy() newkeywords.update(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 または呼び出し可能オブジェクトである必要があります (通常の関数など、両方の性質を持つオブジェクトは記述子として扱われます。)
When func is a descriptor (such as a normal Python function,
classmethod()
,staticmethod()
,abstractmethod()
or another instance ofpartialmethod
), calls to__get__
are delegated to the underlying descriptor, and an appropriatepartial
object returned as the result.func が記述子以外の呼び出し可能オブジェクトである場合、適切な束縛メソッドが動的に作成されます。この func は、メソッドとして使用された場合、Python の通常の関数と同様に動作します。
partialmethod
コンストラクタに args と keywords が渡されるよりも前に、 self 引数が最初の位置引数として挿入されます。以下はプログラム例です:
>>> class Cell(object): ... 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[, initializer])¶ 2 引数の function を iterable の要素に対して左から右に累積的に適用し、イテラブルを単一の値に縮約します。例えば、
reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])
は((((1+2)+3)+4)+5)
を計算します。左引数 x は累計の値で、右引数 y は iterable から取り出した更新値です。オプションの initializer が存在する場合、計算の際にイテラブルの先頭に置かれ、またイテラブルが空の場合のデフォルトになります。initializer が与えられておらず、iterable が単一の要素しか持っていない場合、最初の要素が返されます。およそ次と等価です:
def reduce(function, iterable, initializer=None): it = iter(iterable) if initializer is None: value = next(it) else: value = initializer for element in it: value = function(value, element) return value
-
@
functools.
singledispatch
(default)¶ Transforms a function into a single-dispatch generic function.
汎用関数を定義するには、
@singledispatch
デコレータでデコレートします。ディスパッチは 1 つ目の引数の型で行われることに注意して、関数を次のように作成してください:>>> from functools import singledispatch >>> @singledispatch ... def fun(arg, verbose=False): ... if verbose: ... print("Let me just say,", end=" ") ... print(arg)
関数にオーバーロード実装を追加するには、汎用関数の
register()
属性を使用します。この属性は、型引数を取り、その型の操作を実装する関数をデコレートするデコレータです:>>> @fun.register(int) ... def _(arg, verbose=False): ... if verbose: ... print("Strength in numbers, eh?", end=" ") ... print(arg) ... >>> @fun.register(list) ... def _(arg, verbose=False): ... if verbose: ... print("Enumerate this:") ... for i, elem in enumerate(arg): ... print(i, elem)
register()
属性を関数形式で使用すると、lambda 関数と既存の関数の登録を有効にできます:>>> def nothing(arg, verbose=False): ... print("Nothing.") ... >>> fun.register(type(None), nothing)
The
register()
attribute returns the undecorated function which enables decorator stacking, pickling, as well as creating 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
型に登録されます。これは、他によりよい実装が見つからないことを意味します。指定された型に対して、汎用関数がどの実装を選択するかを確認するには、
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 で追加.
-
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()
のドキュメンテーション文字列は失われてしまいます。