Python

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)   # 두 번의 새로운 재귀 호출을 수행하며, factorial(10)은 캐시되어 있습니다
479001600

캐시는 스레드 세이프(threadsafe)하므로 래핑된 함수를 여러 스레드에서 사용할 수 있습니다. 이는 내부 데이터 구조가 동시 업데이트 중에도 일관성을 유지함을 의미합니다.

첫 번째 호출이 완료되고 캐시되기 전에 다른 스레드에서 추가적인 호출을 수행하는 경우, 래핑된 함수가 여러 번 호출될 수 있습니다.

함수 호출 중에 록이 유지되지 않으므로 한 번만 호출되는 동작(call-once)을 보장할 수 없습니다. 첫 번째 호출이 실행되는 동안 동일한 인자로 다른 호출이 발생할 가능성이 있습니다.

Added in version 3.9.

@functools.cached_property(func)

클래스의 메서드를 값이 한 번 계산된 후 인스턴스 수명 동안 일반 속성(attribute)으로 캐시되는 프로퍼티로 변환합니다. 캐싱 기능이 추가된 @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 는 멀티스레드 사용 시 발생할 수 있는 경쟁 상태(race condition)를 방지하지 않습니다. 게터(getter) 함수가 동일 인스턴스에서 여러 번 실행될 수 있으며, 가장 최근에 실행된 결과가 캐시된 값으로 설정됩니다. 만약 캐시된 프로퍼티가 멱등성이 있거나 인스턴스에서 여러 번 실행되어도 해롭지 않다면 문제없습니다. 동기화가 필요한 경우 데코레이션된 게터 함수 내부나 캐시된 프로퍼티 액세스 주변에 필요한 록을 구현하십시오.

이 데코레이터는 PEP 412 키 공유 딕셔너리의 작동을 방해함에 유의하십시오. 이는 인스턴스 딕셔너리가 평소보다 더 많은 공간을 차지할 수 있음을 의미합니다.

또한, 이 데코레이터는 각 인스턴스의 __dict__ 어트리뷰트가 가변 매핑일 것을 요구합니다. 이는 메타 클래스(형 인스턴스의 __dict__ 어트리뷰트가 클래스 이름 공간에 대한 읽기 전용 프락시이기 때문에)와 __dict__를 정의된 슬롯 중 하나로 포함하지 않고 __slots__를 지정하는 것(이러한 클래스는 __dict__ 어트리뷰트를 전혀 제공하지 않기 때문에)과 같은 일부 형에서 작동하지 않음을 의미합니다.

가변 매핑을 사용할 수 없거나 효율적인 키 공유를 원하는 경우, @lru_cache 위에 @property 를 중첩하여 @cached_property 와 유사한 효과를 얻을 수도 있습니다. 이것이 @cached_property 와 어떻게 다른지에 대한 자세한 내용은 메서드 호출을 캐싱하려면 어떻게 해야 합니까? 를 참조하십시오.

Added in version 3.8.

버전 3.12에서 변경: Python 3.12 이전 버전에서 @cached_property 는 멀티스레드 환경에서 게터 함수가 인스턴스당 한 번만 실행되도록 보장하기 위해 문서화되지 않은 록을 포함했습니다. 그러나 이 록은 인스턴스 단위가 아닌 속성별로 적용되어 비정상적으로 높은 록 경합(lock contention)을 초래할 수 있었습니다. Python 3.12 이후 버전에서는 이 록이 제거되었습니다.

functools.cmp_to_key(func)

구식 비교 함수를 키 함수로 변환합니다. (sorted(), min(), max(), heapq.nlargest(), heapq.nsmallest(), itertools.groupby()와 같은) 키 함수를 받아들이는 도구와 함께 사용됩니다. 이 함수는 주로 비교 함수 사용을 지원하는 파이썬 2에서 변환되는 프로그램의 전이 도구로 사용됩니다.

비교 함수는 두 개의 인자를 받아들이고, 그들을 비교하여, 작으면 음수, 같으면 0, 크면 양수를 반환하는 콜러블입니다. 키 함수는 하나의 인자를 받아들이고 정렬 키로 사용할 다른 값을 반환하는 콜러블입니다.

예:

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 호출까지 저장하는 기억하는(memoizing) 콜러블 함수를 감싸는 데코레이터. 비싸거나 I/O 병목 함수가 같은 인자로 주기적으로 호출될 때 시간을 절약할 수 있습니다.

캐시는 스레드 세이프(threadsafe)하므로 래핑된 함수를 여러 스레드에서 사용할 수 있습니다. 이는 내부 데이터 구조가 동시 업데이트 중에도 일관성을 유지함을 의미합니다.

첫 번째 호출이 완료되고 캐시되기 전에 다른 스레드에서 추가적인 호출을 수행하는 경우, 래핑된 함수가 여러 번 호출될 수 있습니다.

결과를 캐시 하는 데 딕셔너리가 사용되기 때문에, 함수에 대한 위치와 키워드 인자는 해시 가능해야 합니다.

서도 다른 인자 패턴은 별도의 캐시 항목을 갖는 별개의 호출로 간주할 수 있습니다. 예를 들어, f(a=1, b=2)f(b=2, a=1)은 키워드 인자 순서가 다르며 두 개의 개별 캐시 항목을 가질 수 있습니다.

user_function이 지정되면, 콜러블이어야 합니다. 이는 lru_cache 데코레이터를 사용자 함수에 직접 적용 할 수 있도록 하며, maxsize를 기본값 128로 유지합니다:

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

maxsizeNone으로 설정되면, LRU 기능이 비활성화되고 캐시가 제한 없이 커질 수 있습니다.

typed 이 true로 설정되면 서로 다른 유형의 함수 인자가 별도로 캐시됩니다. typed 가 false인 경우, 구현체는 일반적으로 이를 동일한 호출로 간주하고 단 하나의 결과만 캐싱합니다. (단, strint 와 같은 일부 타입은 typed 가 false이더라도 별도로 캐싱될 수 있습니다.)

참고로, 타입의 구체성은 내용이 아니라 함수의 직접적인 인자에만 적용됩니다. 스칼라 인자인 Decimal(42)Fraction(42) 는 서로 다른 결과를 가진 별개의 호출로 취급되지만, 튜플 인자인 ('answer', Decimal(42))('answer', Fraction(42)) 는 동일하게 취급됩니다.

래핑 된 함수는 maxsizetyped의 값을 표시하는 새 dict를 반환하는 cache_parameters() 함수로 인스트루먼트 됩니다. 이것은 정보 제공만을 위한 것입니다. 값을 변경해도 효과가 없습니다.

캐시의 효율성을 측정하고 maxsize 매개변수를 조정하는 데 도움을 주기 위해, 래핑된 함수는 hits, misses, maxsize, currsize 를 나타내는 named tuple 을 반환하는 cache_info() 함수로 인스트루먼트됩니다.

또한 이 데코레이터는 캐시를 삭제하거나 무효화하는 기능을 가진 cache_clear() 함수를 제공합니다.

원래의 하부 함수는 __wrapped__ 어트리뷰트를 통해 액세스 할 수 있습니다. 이것은 인트로스펙션, 캐시 우회 또는 다른 캐시로 함수를 다시 래핑하는 데 유용합니다.

캐시는 인자와 반환 값이 캐시에서 만료되거나 캐시가 초기화될 때까지 해당 값들에 대한 참조를 유지합니다.

메서드가 캐시되는 경우, self 인스턴스 인자가 캐시에 포함됩니다. 자세한 내용은 메서드 호출을 캐싱하려면 어떻게 해야 합니까? 를 참조하십시오.

LRU (least recently used) 캐시는 가장 최근 호출이 향후 호출에 대한 최상의 예측일 때 가장 잘 작동합니다 (예를 들어, 뉴스 서버에서 가장 인기 있는 기사는 매일 바뀌는 경향이 있습니다). 캐시의 크기 제한은 웹 서버와 같은 오래 실행되는 프로세스에서 제한 없이 캐시가 커지지 않도록 합니다.

일반적으로, LRU 캐시는 이전에 계산된 값을 재사용하려고 할 때만 사용해야 합니다. 따라서, 부작용이 있는 함수, 각 호출에서 고유한 가변 객체를 만들어야 하는 함수 (가령 제너레이터와 비동기 함수), time()이나 random()과 같은 비순수(impure) 함수를 캐시 하는 것은 의미가 없습니다.

정적 웹 콘텐츠를 위한 LRU 캐시의 예:

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

동적 프로그래밍(dynamic programming) 기법을 구현하기 위해 캐시를 사용하여 피보나치 수를 효율적으로 계산하는 예:

@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) 순서 메서드를 정의하는 클래스를 주면, 이 클래스 데코레이터가 나머지를 제공합니다. 가능한 모든 풍부한 비교 연산을 지정하는 데 드는 노력이 단순화됩니다:

클래스는 __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()))

참고

이 데코레이터를 사용하면 올바르게 동작하는 전 순서(totally ordered) 형을 쉽게 만들 수 있지만, 파생된 비교 메서드에서 실행 속도가 느려지고 스택 트레이스가 더 복잡해지는 대가를 지불합니다. 성능 벤치마킹이 이것이 특정 응용 프로그램의 병목임을 가리키면, 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)

호출될 때 위치 인자 args와 키워드 인자 keywords로 호출된 func처럼 동작하는 새 partial 객체를 반환합니다. 더 많은 인자가 호출에 제공되면, 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

The partial() function is used for partial function application which “freezes” some portion of a function’s arguments and/or keywords resulting in a new object with a simplified signature. For example, partial() can be used to create a callable that behaves like the int() function where the base argument defaults to 2:

>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18

argsPlaceholder 센티널이 있으면, 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 가 발생합니다. 위치 인자가 하나만 제공되었으나 채워야 할 자리 표시자가 두 개이기 때문입니다.

기존의 partial() 객체에 partial`을 적용하면, 입력 객체의 :data:()!Placeholder` 센티널이 새로운 위치 인자로 채워집니다. 이전 Placeholder`가 차지하던 위치에 새로운 :data:!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`를 :func:!partial`의 키워드 인자로 전달할 수 없습니다.

버전 3.14에서 변경: 위치 인자에서 Placeholder 지원이 추가되었습니다.

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

직접 호출하기보다는 메서드 정의로 사용되도록 설계된 것을 제외하고는 partial과 같이 동작하는 새 partialmethod 디스크립터를 반환합니다.

func디스크립터나 콜러블이어야 합니다 (일반 함수처럼 둘 모두인 객체는 디스크립터로 처리됩니다).

func 이 디스크립터(일반 파이썬 함수, classmethod(), staticmethod(), abstractmethod() 또는 다른 partialmethod 인스턴스 등)인 경우, __get__ 호출은 해당 디스크립터로 위임되며 결과로 적절한 partial 객체 가 반환됩니다.

func가 디스크립터가 아닌 콜러블이면, 적절한 연결된 메서드가 동적으로 만들어집니다. 이것은 메서드로 사용될 때 일반 파이썬 함수처럼 작동합니다: partialmethod 생성자에 제공된 argskeywords보다도 전에 self 인자가 첫 번째 위치 인자로 삽입됩니다.

예:

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

두 인자의 function을 왼쪽에서 오른쪽으로 iterable의 항목에 누적적으로 적용해서, 이터러블을 단일 값으로 줄입니다. 예를 들어, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])((((1+2)+3)+4)+5)를 계산합니다. 왼쪽 인자 x는 누적값이고 오른쪽 인자 yiterable에서 온 갱신 값입니다. 선택적 initial가 있으면, 계산에서 이터러블의 항목 앞에 배치되고, 이터러블이 비어있을 때 기본값의 역할을 합니다. initial가 제공되지 않고 iterable에 하나의 항목만 포함되면, 첫 번째 항목이 반환됩니다.

대략 다음과 동등합니다:

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)

함수에 오버로드된 구현을 추가하려면 일반 함수(generic function)의 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() 속성은 데코레이트되지 않은 함수를 반환합니다. 이를 통해 데코레이터 중첩, 피클링, 그리고 각 변형에 대한 독립적인 단위 테스트 생성이 가능해집니다:

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

호출되면, 제네릭 함수는 첫 번째 인자의 형에 따라 디스패치 합니다:

>>> 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 형으로 등록되어서, 더 나은 구현이 발견되지 않으면 사용됩니다.

구현이 추상 베이스 클래스에 등록되면, 베이스 클래스의 가상 서브 클래스는 그 구현으로 디스패치 됩니다:

>>> 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)    # 참고: 기본 구현
<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 가 가장 바깥쪽(outermost) 데코레이터여야 합니다. 여기는 neg 메서드가 인스턴스가 아닌 클래스에 바인딩된 Negator 클래스 예시입니다:

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에서 변경: Added support of non-descriptor callables.

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

wrapped 함수처럼 보이도록 wrapper 함수를 갱신합니다. 선택적 인자는 원래 함수의 어떤 어트리뷰트가 wrapper 함수의 일치하는 어트리뷰트에 직접 대입되고 wrapper 함수의 어떤 어트리뷰트가 원래 함수의 해당 어트리뷰트로 갱신되는지 지정하는 튜플입니다. 이 인자들의 기본값은 모듈 수준 상수 WRAPPER_ASSIGNMENTS(wrapper 함수의 __module__, __name__, __qualname__, __annotations__, __type_params____doc__ 독스트링에 대입합니다)와 WRAPPER_UPDATES(wrapper 함수의 __dict__, 즉 인스턴스 딕셔너리를 갱신합니다)입니다.

내부 검사 및 기타 목적(예를 들어 @lru_cache`와 같은 캐싱 데코레이터 우회)을 위해 원본 함수에 접근할 있도록, 함수는 래핑된 함수를 가리키는 ``__wrapped__` 어트리뷰트를 래퍼에 자동으로 추가합니다.

이 함수의 주요 용도는 데코레이트 된 함수를 래핑하고 wrapper를 반환하는 데코레이터 함수에서 사용하는 것입니다. wrapper 함수가 갱신되지 않으면, 반환된 함수의 메타 데이터는 원래 함수 정의가 아닌 wrapper 정의를 반영하게 되어 일반적으로 도움이 되지 않습니다.

update_wrapper()는 함수 이외의 콜러블과 함께 사용할 수 있습니다. 래핑 되는 객체에서 누락된 assignedupdated로 이름 지정된 어트리뷰트는 무시됩니다 (즉, 이 함수는 wrapper 함수에서 그 어트리뷰트를 설정하려고 시도하지 않습니다). wrapper 함수 자체에 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() 의 독스트링(docstring)도 소실되었을 것입니다.

partial 객체

partial 객체는 partial()이 만든 콜러블 객체입니다. 세 가지 읽기 전용 어트리뷰트가 있습니다:

partial.func

콜러블 객체나 함수. partial 객체에 대한 호출은 새로운 인자와 키워드와 함께 func로 전달됩니다.

partial.args

partial 객체 호출에 제공되는 위치 인자 앞에 추가될 가장 왼쪽 위치 인자들입니다.

partial.keywords

partial 객체가 호출될 때 제공될 키워드 인자들입니다.

partial 객체는 호출 가능하고(callable), 약한 참조가 가능하며(weak referenceable), 어트리뷰트를 가질 수 있다는 점에서 함수 객체 와 유사합니다. 그러나 몇 가지 중요한 차이점이 있습니다. 예를 들어, __name____doc__ 어트리뷰트는 자동으로 생성되지 않습니다.