Python 標準ライブラリ functools 関数ツール
Publish date: 2021-04-11
Python標準ライブラリfunctoolsを使うと、高階関数やアノテーションの形で関数の挙動に変更を加える事ができます。 例えば、関数の実行結果をキャッシュしたり、関数をラップしたりといった操作を行えます。
functoolsで提供されている関数
@cache(user_function) 関数のキャッシュ
lru_cache(maxsize=None)と同様。引数と結果のマップを保持して関数のキャッシュを行う。
@functools.cache
def factorial(n):
print('call', n)
return n * factorial(n-1) if n else 1
factorial(5) # call 5 ~ call 0 # => 120
factorial(10) # call 10 ~ call 6 # => 3628800
factorial(7) # => 5040
@cached_property(func) プロパティのキャッシュ
class SampleClass:
@functools.cached_property
def c_prop(self):
print('call c_prop')
return 'c_prop_value'
sc = SampleClass()
sc.c_prop # call c_prop # => 'c_prop_value'
sc.c_prop #=> 'c_prop_value'
@cache
と@property
を組み合わせる事でもプロパティのキャッシュを実現できる。
class SampleClass:
@property
@functools.cache
def c_prop(self):
print('call c_prop')
return 'c_prop_value'
sc = SampleClass()
sc.c_prop #=> call c_prop # => 'c_prop_value'
sc.c_prop #=> 'c_prop_value'
@lru_cache(user_function) 関数の最近呼び出しキャッシュ
@functools.lru_cache
def factorial(n):
print('call', n)
return n * factorial(n-1) if n else 1
factorial(5) # call 5 ~ call 0 # => 120
factorial(10) # call 10 ~ call 6 # => 3628800
factorial(7) # => 5040
@lru_cache(maxsize=128, typed=False) 関数の最近呼び出しキャッシュ
maxsizeはキャッシュの上限数、typedはtrueで型が相違した場合にキャッシュする。
@functools.lru_cache(maxsize=3)
def factorial(n):
print('call', n)
return n * factorial(n-1) if n else 1
# キャッシュの設定情報
factorial.cache_parameters() # => {'maxsize': 3, 'typed': False}
factorial(5) # call 5 ~ call 0 # => 120
factorial(10) # call 10 ~ call 0 # => 3628800
factorial(7) # => 5040
# キャッシュの情報
factorial.cache_info()
# => CacheInfo(hits=1, misses=19, maxsize=3, currsize=3)
# キャッシュを回避してアクセス
factorial.__wrapped__(7) # call 7 # => 5040
@total_ordering 順序比較の残りの実装
__lt__(), __le__(), __gt__(), __ge__()
の1つ以上と__eq__()
メソッドから残りを実装する。
@functools.total_ordering
class ValueClass:
def __init__(self, value):
self.value = value
def __eq__(self, other):
return self.value == other.value
def __lt__(self, other):
return self.value < other.value
vc1 = ValueClass(1)
vc2_1 = ValueClass(2)
vc2_2 = ValueClass(2)
vc1 < vc2_1 # => True
vc1 > vc2_1 # => False
vc2_1 == vc2_2 # => True
vc2_1 != vc2_2 # => False
vc1 <= vc2_1 # => True
vc2_1 >= vc2_2 # => True
partial(func, /, *args, **keywords) 一部引数を指定した関数
not_zero_filter = functools.partial(filter, lambda x: x!=0)
list(not_zero_filter([1,2,0,2,0,3])) # => [1, 2, 2, 3]
partialmethod(func, /, *args, **keywords) 一部引数を指定したメソッドの定義
class SampleClass:
def get_multiples(self, number , size):
return list(range(number, number*size+number, number))
get_even = functools.partialmethod(get_multiples, 2)
get_10th = functools.partialmethod(get_multiples, 10)
sc = SampleClass()
sc.get_multiples(3, 4) # => [3, 6, 9, 12]
sc.get_even(5) # => [2, 4, 6, 8, 10]
sc.get_10th(4) # => [10, 20, 30, 40]
reduce(function, iterable[, initializer]) イテレータから値を取得
functools.reduce(lambda x,y:x+y,[1,2,3,4,5]) # => 15
functools.reduce(lambda x,y:x+y,[1,2,3,4,5],10) # => 25
@singledispatch ジェネリック関数の定義
from decimal import Decimal
# ジェネリック関数定義
@functools.singledispatch
def generic_print(obj):
print("generic_print ", obj)
# 型アノテーションでオーバーロード
@generic_print.register
def _(obj: int):
print("int ", obj)
# register属性に型を指定してオーバーロード(複数指定も可能)
@generic_print.register(float)
@generic_print.register(Decimal)
def _(obj):
print("float or Decimal ", obj)
# registerを関数形式で呼び出し
def list_print(obj:list):
for item in obj:
print("list_print ", item)
generic_print.register(list, list_print)
# lamnbda式
generic_print.register(type(None), lambda x:print("None ", x))
generic_print("Test String") # => generic_print Test String
generic_print(1) # => int 1
generic_print(1.1) # => float or Decimal 1.1
generic_print(Decimal("5.00")) # => float or Decimal 5.00
generic_print(list([1, 2, 3])) # => list_print 1 list_print 2 list_print 3
generic_print(None) # => None None
generic_print.registry.keys()
# => dict_keys([<class 'object'>, <class 'int'>, <class 'decimal.Decimal'>, <class 'float'>, <class 'list'>, <class 'NoneType'>])
@singledispatchmethod(func) ジェネリック関数のメソッド定義
class SampleClass:
@functools.singledispatchmethod
def generic_print(self, obj):
raise NotImplementedError("generic_print")
# 型アノテーションでオーバーロード
@generic_print.register
def _(self, obj: int):
print("int ", obj)
# register属性に型を指定してオーバーロード(複数指定も可能)
@generic_print.register(float)
@generic_print.register(Decimal)
def _(self, obj):
print("float or Decimal ", obj)
sc = SampleClass()
sc.generic_print(1) # => int 1
sc.generic_print(1.1) # => float or Decimal 1.1
sc.generic_print(Decimal("5.00")) # => float or Decimal 5.00
sc.generic_print("Test String") # => NotImplementedError: generic_print
update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES) 関数のラップ
def decorator(f):
def wrapper(*args, **kwds):
print("decorator wrapper begin")
print(args)
print(kwds)
print("wrapped enter")
print(f'WRAPPER_ASSIGNMENTS : {functools.WRAPPER_ASSIGNMENTS}')
print(f'WRAPPER_UPDATES : {functools.WRAPPER_UPDATES}')
functools.update_wrapper(wrapper, f)
result = f(*args, **kwds)
print("wrapped exit")
print("decorator wrapper end")
return result
return wrapper
@decorator
def sample_function(*args, **kwds):
"""Docstring sample_function"""
print("sample_function")
sample_function(123,"abc",a=1,b=1)
sample_function.__name__ # => 'sample_function'
sample_function.__doc__ # => 'Docstring sample_function'
wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES) 関数のラップ
def decorator(f):
@functools.wraps(f)
def wrapper(*args, **kwds):
print("decorator wrapper begin")
print(args)
print(kwds)
print("wrapped enter")
result = f(*args, **kwds)
print("wrapped exit")
print("decorator wrapper end")
return result
return wrapper
@decorator
def sample_function(*args, **kwds):
"""Docstring sample_function"""
print("sample_function")
sample_function(123,"abc",a=1,b=1)
sample_function.__name__ # => 'sample_function'
sample_function.__doc__ # => 'Docstring sample_function'
単にデコレータを定義すると、ラップされた関数の情報が一部失われる。
def decorator(f):
def wrapper(*args, **kwds):
print("decorator wrapper begin")
print(args)
print(kwds)
print("wrapped enter")
result = f(*args, **kwds)
print("wrapped exit")
print("decorator wrapper end")
return result
return wrapper
@decorator
def sample_function(*args, **kwds):
"""Docstring sample_function"""
print("sample_function")
sample_function(123,"abc",a=1,b=1)
sample_function.__name__ # => ''
sample_function.__doc__ # => ''