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')
maxsize가
None으로 설정되면, LRU 기능이 비활성화되고 캐시가 제한 없이 커질 수 있습니다.typed 이 true로 설정되면 서로 다른 유형의 함수 인자가 별도로 캐시됩니다. typed 가 false인 경우, 구현체는 일반적으로 이를 동일한 호출로 간주하고 단 하나의 결과만 캐싱합니다. (단, str 및 int 와 같은 일부 타입은 typed 가 false이더라도 별도로 캐싱될 수 있습니다.)
참고로, 타입의 구체성은 내용이 아니라 함수의 직접적인 인자에만 적용됩니다. 스칼라 인자인
Decimal(42)와Fraction(42)는 서로 다른 결과를 가진 별개의 호출로 취급되지만, 튜플 인자인('answer', Decimal(42))와('answer', Fraction(42))는 동일하게 취급됩니다.래핑 된 함수는 maxsize와 typed의 값을 표시하는 새
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 theint()function where the base argument defaults to2:>>> basetwo = partial(int, base=2) >>> basetwo.__doc__ = 'Convert base 2 string to an int.' >>> basetwo('10010') 18
args 에
Placeholder센티널이 있으면,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생성자에 제공된 args와 keywords보다도 전에 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는 누적값이고 오른쪽 인자 y는 iterable에서 온 갱신 값입니다. 선택적 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()는 함수 이외의 콜러블과 함께 사용할 수 있습니다. 래핑 되는 객체에서 누락된 assigned나 updated로 이름 지정된 어트리뷰트는 무시됩니다 (즉, 이 함수는 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 객체는 호출 가능하고(callable), 약한 참조가 가능하며(weak referenceable), 어트리뷰트를 가질 수 있다는 점에서 함수 객체 와 유사합니다. 그러나 몇 가지 중요한 차이점이 있습니다. 예를 들어, __name__ 및 __doc__ 어트리뷰트는 자동으로 생성되지 않습니다.