이 짧은 기사에 표시된 코드는 형식화된 데코레이터를 제공하는 계약에 의해 설계된 작은 오픈 소스 프로젝트에서 가져온 것입니다. 데코레이터는 매우 유용한 개념이며 온라인에서 확실히 많은 것을 찾을 수 있습니다. 간단히 말해, 데코레이팅된 함수가 호출될 때마다(전과 후에) 코드가 실행될 수 있습니다. 이 방법으로 함수 매개변수 또는 반환 값을 수정하고, 실행 시간을 측정하고, 로깅을 추가하고, 실행 시간 유형 확인을 수행하는 등의 작업을 수행할 수 있습니다. 데코레이터는 클래스용으로 작성되어 또 다른 메타프로그래밍 접근 방식을 제공할 수도 있습니다(예: attrs 패키지에서 수행).
가장 간단한 형태로 데코레이터는 다음 코드와 같이 정의됩니다.
def my_first_decorator(func): def wrapped(*args, **kwargs): # do something before result = func(*args, **kwargs) # do something after return result return wrapped @my_first_decorator def func(a): return a
위 코드와 같이, 래핑된 중첩 함수가 정의되면 함수 내에서 주변 변수에 액세스할 수 있으며 함수가 어딘가에서 사용되는 한 메모리에 보관될 수 있습니다(함수 프로그래밍 언어에서는 클로저라고 함).
매우 간단하지만 몇 가지 단점이 있습니다. 가장 큰 문제는 데코레이팅된 함수가 이전 함수 이름(inspect.signature를 통해 확인할 수 있음), 문서 문자열, 심지어 이름까지도 잃게 된다는 점입니다. 이는 소스 코드 문서화 도구(예: sphinx)의 문제입니다. 표준 라이브러리의 functools.wraps 데코레이터를 사용하면 쉽게 해결할 수 있습니다.
from functools import wraps from typing import Any, Callable, TypeVar, ParamSpec P = ParamSpec("P") # 需要python >= 3.10 R = TypeVar("R") def my_second_decorator(func: Callable[P, R]) -> Callable[P, R]: @wraps(func) def wrapped(*args: Any, **kwargs: Any) -> R: # do something before result = func(*args, **kwargs) # do something after return result return wrapped @my_second_decorator def func2(a: int) -> int: """Does nothing""" return a print(func2.__name__) # 'func2' print(func2.__doc__) # 'Does nothing'
이 예에서는 유형 주석을 추가했습니다. 주석 및 유형 힌트는 Python에 추가된 가장 중요한 추가 사항입니다. 더 나은 가독성, IDE의 코드 완성, 더 큰 코드 기반의 유지 관리 가능성은 몇 가지 예에 불과합니다. 위의 코드는 이미 대부분의 사용 사례를 다루고 있지만 데코레이터는 매개변수화할 수 없습니다. 함수의 실행 시간을 기록하는 데코레이터를 작성하는 것을 고려해보세요. 단, 특정 시간(초)을 초과하는 경우에만 해당됩니다. 이 번호는 장식된 각 기능에 대해 개별적으로 구성할 수 있어야 합니다. 지정하지 않으면 기본값을 사용해야 하며 데코레이터는 괄호 없이 사용해야 사용하기 쉽습니다.
@time(threshold=2) def func1(a): ... # No paranthesis when using default threshold @time def func2(b): ...
두 번째 경우에 괄호를 사용할 수 있거나 기본값을 제공하지 않는 경우 매개변수가 전혀 없으면 이 팁으로 충분합니다:
from functools import wraps from typing import Any, Callable, TypeVar, ParamSpec P = ParamSpec("P") # 需要python >= 3.10 R = TypeVar("R") def my_third_decorator(threshold: int = 1) -> Callable[[Callable[P, R]], Callable[P, R]]: def decorator(func: Callable[P, R]) -> Callable[P, R]: @wraps(func) def wrapper(*args: Any, **kwargs: Any) -> R: # do something before you can use `threshold` result = func(*args, **kwargs) # do something after return result return wrapper return decorator @my_third_decorator(threshold=2) def func3a(a: int) -> None: ... # works @my_third_decorator() def func3b(a: int) -> None: ... # Does not work! @my_third_decorator def func3c(a: int) -> None: ...
세 번째 경우를 다루기 위해, 실제로 선택적 매개변수를 추가하는 것 이상의 일을 할 수 있는 패키지, 즉 랩과 데코레이터가 있습니다. 품질은 매우 높지만 상당한 추가 복잡성이 발생합니다. Wrapt로 장식된 함수를 사용하면 원격 클러스터에서 함수를 실행할 때 직렬화 문제가 추가로 발생했습니다. 내가 아는 한, 둘 다 완전히 형식화되지 않았으므로 정적 형식 검사기/린터(예: mypy)는 엄격 모드에서 실패합니다.
나는 나만의 패키지를 작업할 때 이러한 문제를 해결해야 했고 나만의 솔루션을 작성하기로 결정했습니다. 재사용은 쉽지만 라이브러리로 변환하기는 어려운 패턴이 됩니다.
표준 라이브러리의 오버로드된 데코레이터를 사용합니다. 이런 방식으로 동일한 데코레이터를 매개변수 없는 데코레이터와 함께 사용하도록 지정할 수 있습니다. 그 외에는 위의 두 스니펫을 조합한 것입니다. 이 접근 방식의 한 가지 단점은 모든 매개변수를 키워드 인수로 제공해야 한다는 것입니다(결국 가독성이 높아집니다).
from typing import Callable, TypeVar, ParamSpec from functools import partial, wraps P = ParamSpec("P") # requires python >= 3.10 R = TypeVar("R @overload def typed_decorator(func: Callable[P, R]) -> Callable[P, R]: ... @overload def typed_decorator(*, first: str = "x", second: bool = True) -> Callable[[Callable[P, R]], Callable[P, R]]: ... def typed_decorator( func: Optional[Callable[P, R]] = None, *, first: str = "x", second: bool = True ) -> Union[Callable[[Callable[P, R]], Callable[P, R]], Callable[P, R]]: """ Describe what the decorator is supposed to do! Parameters ---------- first : str, optional First argument, by default "x". This is a keyword-only argument! second : bool, optional Second argument, by default True. This is a keyword-only argument! """ def wrapper(func: Callable[P, R], *args: Any, **kw: Any) -> R: """The actual logic""" # Do something with first and second and produce a `result` of type `R` return result # Without arguments `func` is passed directly to the decorator if func is not None: if not callable(func): raise TypeError("Not a callable. Did you use a non-keyword argument?") return wraps(func)(partial(wrapper, func)) # With arguments, we need to return a function that accepts the function def decorator(func: Callable[P, R]) -> Callable[P, R]: return wraps(func)(partial(wrapper, func)) return decorator
나중에 매개변수 없이 별도로 데코레이터를 사용할 수 있습니다.
@typed_decorator def spam(a: int) -> int: return a @typed_decorator(first = "y def eggs(a: int) -> int: return a
이 모델에는 확실히 약간의 오버헤드가 있습니다. 하지만 비용보다 이점이 더 큽니다.
원문:https://www.php.cn/link/d0f82e1046ccbd597c7f2a7bfba9e7dd
위 내용은 매개변수가 포함된 완전한 유형의 Python 데코레이터의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!