본문 바로가기
Python

[Python] functools module

by llHoYall 2021. 5. 8.

The functools module is for higher-order functions.

@functools.cache(func)

Simple lightweight unbounded function cache. Sometimes called memoize.

Returns the same as lru_cache(maxsize=None).

This function is smaller and faster than lru_cache() with a size limit because it never needs to evict old values.

v3.9 or later
from functools import cache

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

factorial(10)
# 4.6393000000033435e-05

factorial(12)
# 5.3059999999827134e-06

@functools.cached_property(func)

Transform a method of a class into a property whose value is computed once and then cached as a normal attribute for the life of the instance.

Regular property() blocks attribute writes unless a setter is defined. In contrast, cached_property() allows writes.

The cached_property() decorator only runs on lookups and when an attribute of the same name doesn't exist.

The cached value can be cleared by deleting the attribute. This allows the cached_property() function to run again.

v3.8 or later
from functools import cached_property

class Sample:
    def __init__(self):
      self._result = 50
  
    @cached_property
    def increase(self):
        self._result = self._result + 50
        return self._result
  
obj = Sample()
print(obj.increase)
# 100
print(obj.increase)
# 100
print(obj.increase)
# 100

@functions.cmp_to_key(func)

Transform an old-style comparison function to a key function.

Used with tools that accept key functions (such as sorted(), min(), max(), heapq.nlargest(), heapq.nsmallesst(), itertools.groupby())

from functools import cmp_to_key

def compare(a, b):
    print(f"Comparing {a}, {b}")
    if a > b:
        return 1
    elif a < b:
        return -1
    else:
        return 0
  
print(sorted([4, 1, 3, 2], key=cmp_to_key(compare)))
# Comparing 1, 4
# Comparing 3, 1
# Comparing 3, 4
# Comparing 3, 1
# Comparing 2, 3
# Comparing 2, 1
# [1, 2, 3, 4]

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

Decorator to wrap a function with a memorizing callable that sves up to the maxsize most recent calls.

from functools import lru_cache

@lru_cache
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

fibonacci(30)

@functools.partial(func, /, *args, **kwargs)

Return a new partial object which when called will behave like func called with the positional arguments args and keyword arguments kwargs.

If more arguments are supplied to the call, they are appended to args.

If additional keyword arguments are supplied, they extend and override keywords.

from functools import partial

def f(a, b, c):
    return 100 * a + 10 * b + c

g = partial(f, 3, 5)
print(g(4))
# 354

h = partial(f, c=2, b=1)
print(h(3))
# 312

@functools.partialmethod(func, /, *args, **kwargs)

Return a new partialmethod descriptor that behaves like partial except that it is designed to be used as a method definition rather than being directly callable.

from functools import partialmethod

class Color:
    def __init__(self):
        self._color = 'white'

    def set_color(self, color):
        self._color = color
  
    set_red = partialmethod(set_color, color='red')
  
color = Color()
print(color._color)
# white

color.set_red()
print(color._color)
# red

@functools.reduce(func, iterable[, initializer])

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.

from functools import reduce

print(reduce(lambda a, b: a + b, [1, 2, 3, 4]))
# (((1 + 2) + 3) + 4)
# 10

print(reduce(lambda a, b: a * b, [1, 2, 3, 4], 3))
# (((3 * (1 * 2)) * 3) * 4)
# 72

@functools.singledispatch

Transform a function into a single-dispatch generic function.

To add overloaded implementations to the function, use the register() attribute of the generic function.

For functions annotated with types, the decorator will infer the type of the first argument automatically.

from functools import singledispatch

@singledispatch
def process(num=None):
    raise NotImplementedError("Not Implemented")

@process.register(int)
def sub_process(num):
    return f"Integer value: {num}"

@process.register(float)
def sub_process(num):
    return f"Float value: {num}"

print(process(5))
# Integer value: 5

print(process(3.14))
# Float value: 3.14

print(process("test"))
# NotImplementedError: Not Implemented

@functools.singledispatchmethod(func)

Transform a method into a single-dispatch generic function.

from functools import singledispatchmethod

class Dispatcher:
    @singledispatchmethod
    def process(self, arg):
        raise NotImplementedError("Not Implemented")

    @process.register(bool)
    def _(self, num):
        return f"Boolean value: {num}"

    @process.register(int)
    def _(self, num):
        return f"Integer value: {num}"

d = Dispatcher()
print(d.process(True))
# Boolean value: True

print(d.process(5))
# Integer value: 5

print(d.process(2.718))
# NotImplementedError: Not Implemented

@functools.total_ordering

Given a class defining one or more rich comparison ordering methods, this class decorator supplies the rest.

The class must define one of __lt__(), __le__(), __gt__(), or __ge__(). In addition, the class should supply an __eq__() method.

from functools import total_ordering

@total_ordering
class Students:
    def __init__(self, score):
        self.score = score

    def __lt__(self, other):
        return self.score < other.score

    def __eq__(self, other):
        return self.score == other.score

    def __le__(self, other):
        return self.score <= other.score

    def __ge__(self, other):
        return self.score >= other.score

    def __ne__(self, other):
        return self.score != other.score

a = Students(5)
b = Students(7)

print(a < b)
# True
print(a <= b)
# True
print(a > b)
# False
print(a >= b)
# False
print(a == b)
# False
print(a != b)
# True

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

Update a wrapper function to look like the wrapped function.

from functools import update_wrapper
  
def decorator(func):
    def wrapper():
        print("This is wrapper function.")
        func()
          
    print("This is decorator function.")
    update_wrapper(wrapper, func)
    return wrapper
      
@decorator
def wrapped():
    print("This is wrapped function.")
  
wrapped()
# This is decorator function.
# This is wrapper function.
# This is wrapped function.

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

This is a convenience function for invoking update_wrapper() as a function decorator when defining a wrapper function.

It is equivalent to partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated).

from functools import wraps
  
def decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        func(*args, **kwargs)
    return wrapper
  
@decorator
def wrapped1():
    print("This is the first wrapped function.")
  
@decorator
def wrapped2(*args):
    print(f"This is the second wrapped function.")
    for i in args:
        print(i)

@decorator
def wrapped3(**kwargs):
    print(f"This is the third wrapped function.")
    for k, v in kwargs.items():
        print(f"{k}: {v}")

wrapped1()
# This is the first wrapped function.

wrapped2([1, 2, 3])
# This is the second wrapped function.
# [1, 2, 3]

wrapped3(key1=3, key2='hi')
# This is the third wrapped function.
# key1: 3
# key2: hi

'Python' 카테고리의 다른 글

[Python] Draw shapes using tkinter  (0) 2021.05.29
[Python] itertools module  (0) 2021.05.15
[Python] ChainMap in collections  (0) 2021.05.02
[Python] namedtuple in collections  (0) 2021.05.02
[Python] Counter in collections  (0) 2021.05.01

댓글