DEV Community

loading...

Python 標準ライブラリ functools 関数ツール

maru3kaku4kaku profile image maru3kaku4kaku Originally published at marusankakusikaku.jp ・3 min read

Python標準ライブラリfunctoolsを使うと、高階関数やアノテーションの形で関数の挙動に変更を加える事ができます。
例えば、関数の実行結果をキャッシュしたり、関数をラップしたりといった操作を行えます。

itertoolsで提供されている関数

@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
Enter fullscreen mode Exit fullscreen mode

@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'
Enter fullscreen mode Exit fullscreen mode

@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'
Enter fullscreen mode Exit fullscreen mode

@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

Enter fullscreen mode Exit fullscreen mode

@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
Enter fullscreen mode Exit fullscreen mode

@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
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

@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
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

単にデコレータを定義すると、ラップされた関数の情報が一部失われる。

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__ # => ''
Enter fullscreen mode Exit fullscreen mode

Discussion (0)

Forem Open with the Forem app