>  기사  >  백엔드 개발  >  매개변수가 포함된 완전한 유형의 Python 데코레이터

매개변수가 포함된 완전한 유형의 Python 데코레이터

WBOY
WBOY앞으로
2023-04-13 17:58:061329검색

매개변수가 포함된 완전한 유형의 Python 데코레이터

이 짧은 기사에 표시된 코드는 형식화된 데코레이터를 제공하는 계약에 의해 설계된 작은 오픈 소스 프로젝트에서 가져온 것입니다. 데코레이터는 매우 유용한 개념이며 온라인에서 확실히 많은 것을 찾을 수 있습니다. 간단히 말해, 데코레이팅된 함수가 호출될 때마다(전과 후에) 코드가 실행될 수 있습니다. 이 방법으로 함수 매개변수 또는 반환 값을 수정하고, 실행 시간을 측정하고, 로깅을 추가하고, 실행 시간 유형 확인을 수행하는 등의 작업을 수행할 수 있습니다. 데코레이터는 클래스용으로 작성되어 또 다른 메타프로그래밍 접근 방식을 제공할 수도 있습니다(예: 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 51cto.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제