장식된 기능의 서명 보존
제가 매우 일반적인 것을 하는 장식가를 썼다고 가정해 보겠습니다.예를 들어, 모든 인수를 특정 유형으로 변환하고, 로깅을 수행하고, 메모화를 구현하는 등의 작업을 수행할 수 있습니다.
다음은 예입니다.
def args_as_ints(f):
def g(*args, **kwargs):
args = [int(x) for x in args]
kwargs = dict((k, int(v)) for k, v in kwargs.items())
return f(*args, **kwargs)
return g
@args_as_ints
def funny_function(x, y, z=3):
"""Computes x*y + 2*z"""
return x*y + 2*z
>>> funny_function("3", 4.0, z="5")
22
지금까지는 모든 것이 잘 되었습니다.하지만 한 가지 문제가 있습니다.장식된 기능은 원래 기능의 문서화를 유지하지 않습니다.
>>> help(funny_function)
Help on function g in module __main__:
g(*args, **kwargs)
다행히도 다음과 같은 해결 방법이 있습니다.
def args_as_ints(f):
def g(*args, **kwargs):
args = [int(x) for x in args]
kwargs = dict((k, int(v)) for k, v in kwargs.items())
return f(*args, **kwargs)
g.__name__ = f.__name__
g.__doc__ = f.__doc__
return g
@args_as_ints
def funny_function(x, y, z=3):
"""Computes x*y + 2*z"""
return x*y + 2*z
이번에는 기능 이름과 설명서가 정확합니다.
>>> help(funny_function)
Help on function funny_function in module __main__:
funny_function(*args, **kwargs)
Computes x*y + 2*z
하지만 여전히 문제가 있습니다. 기능 서명이 잘못되었다는 것입니다."*args, **kwargs"라는 정보는 거의 쓸모가 없습니다.
무엇을 해야 하나?단순하지만 결함이 있는 두 가지 해결 방법을 생각해 볼 수 있습니다.
1 - 문서 문자열에 올바른 서명을 포함합니다.
def funny_function(x, y, z=3):
"""funny_function(x, y, z=3) -- computes x*y + 2*z"""
return x*y + 2*z
이것은 중복으로 인해 좋지 않습니다.서명은 여전히 자동으로 생성된 설명서에 제대로 표시되지 않습니다.기능을 업데이트하고 문서 문자열을 변경하거나 오타를 내는 것을 잊기 쉽습니다.[그리고 네, 저는 docstring이 이미 함수 본문을 복제했다는 사실을 알고 있습니다. 무시해 주세요. funny_function은 임의의 예일 뿐입니다.]
2 - 모든 특정 서명에 장식기를 사용하거나 특수 용도의 장식기를 사용하지 마십시오.
def funny_functions_decorator(f):
def g(x, y, z=3):
return f(int(x), int(y), z=int(z))
g.__name__ = f.__name__
g.__doc__ = f.__doc__
return g
이것은 동일한 서명을 가진 함수 집합에 대해서는 잘 작동하지만 일반적으로 쓸모가 없습니다.처음에 말했듯이, 저는 장식가들을 완전히 일반적으로 사용할 수 있기를 원합니다.
저는 완전히 일반적이고 자동적인 해결책을 찾고 있습니다.
그래서 궁금한 것은 장식된 기능 서명을 만든 후에 편집할 수 있는 방법이 있는가 하는 것입니다.
그렇지 않으면, 장식된 기능을 구성할 때 "*kwargs, *kwargs" 대신 기능 서명을 추출하여 해당 정보를 사용하는 데코레이터를 작성해도 되겠습니까?그 정보를 어떻게 추출합니까?exec과 함께 어떻게 장식된 기능을 구성해야 합니까?
다른 접근법은?
데코레이터 모듈 설치:
$ pip install decorator정의를 합니다.
args_as_ints():import decorator @decorator.decorator def args_as_ints(f, *args, **kwargs): args = [int(x) for x in args] kwargs = dict((k, int(v)) for k, v in kwargs.items()) return f(*args, **kwargs) @args_as_ints def funny_function(x, y, z=3): """Computes x*y + 2*z""" return x*y + 2*z print funny_function("3", 4.0, z="5") # 22 help(funny_function) # Help on function funny_function in module __main__: # # funny_function(x, y, z=3) # Computes x*y + 2*z
파이썬 3.4+
functools.wraps() from stdlib은 Python 3.4 이후 서명을 보존합니다.
import functools
def args_as_ints(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
args = [int(x) for x in args]
kwargs = dict((k, int(v)) for k, v in kwargs.items())
return func(*args, **kwargs)
return wrapper
@args_as_ints
def funny_function(x, y, z=3):
"""Computes x*y + 2*z"""
return x*y + 2*z
print(funny_function("3", 4.0, z="5"))
# 22
help(funny_function)
# Help on function funny_function in module __main__:
#
# funny_function(x, y, z=3)
# Computes x*y + 2*z
functools.wraps()적어도 Python 2.5 이후에는 사용할 수 있지만 서명을 보존하지는 않습니다.
help(funny_function)
# Help on function funny_function in module __main__:
#
# funny_function(*args, **kwargs)
# Computes x*y + 2*z
알림:*args, **kwargs에 x, y, z=3.
라이브러리인 Python에서 되었습니다.functools"랩된 함수처럼 보이도록 래퍼 함수를 업데이트"하도록 설계되었습니다.그러나 아래와 같이 Python 버전에 따라 동작이 달라집니다.질문의 예제에 적용하면 코드는 다음과 같습니다.
from functools import wraps
def args_as_ints(f):
@wraps(f)
def g(*args, **kwargs):
args = [int(x) for x in args]
kwargs = dict((k, int(v)) for k, v in kwargs.items())
return f(*args, **kwargs)
return g
@args_as_ints
def funny_function(x, y, z=3):
"""Computes x*y + 2*z"""
return x*y + 2*z
Python 3에서 실행하면 다음이 생성됩니다.
>>> funny_function("3", 4.0, z="5")
22
>>> help(funny_function)
Help on function funny_function in module __main__:
funny_function(x, y, z=3)
Computes x*y + 2*z
그러나 Python 2에서는 함수의 인수 목록을 업데이트하지 않는다는 것이 유일한 단점입니다.파이썬 2에서 실행되면 다음을 생성합니다.
>>> help(funny_function)
Help on function funny_function in module __main__:
funny_function(*args, **kwargs)
Computes x*y + 2*z
다음을 포함하는 장식기 모듈이 있습니다.decorator사용할 수 있는 장식기:
@decorator
def args_as_ints(f, *args, **kwargs):
args = [int(x) for x in args]
kwargs = dict((k, int(v)) for k, v in kwargs.items())
return f(*args, **kwargs)
그런 다음 메서드의 서명과 도움말이 보존됩니다.
>>> help(funny_function)
Help on function funny_function in module __main__:
funny_function(x, y, z=3)
Computes x*y + 2*z
편집: J. F. 세바스찬은 제가 수정하지 않았다고 지적했습니다.args_as_ints함수 - 이제 수정되었습니다.
데코레이터 모듈, 특히 이 문제를 해결하는 데코레이터 모듈을 살펴봅니다.
두 번째 옵션:
- 랩 모듈 설치:
이지_설치 랩
랩은 보너스가 있고, 클래스 서명을 유지합니다.
import wrapt
import inspect
@wrapt.decorator
def args_as_ints(wrapped, instance, args, kwargs):
if instance is None:
if inspect.isclass(wrapped):
# Decorator was applied to a class.
return wrapped(*args, **kwargs)
else:
# Decorator was applied to a function or staticmethod.
return wrapped(*args, **kwargs)
else:
if inspect.isclass(instance):
# Decorator was applied to a classmethod.
return wrapped(*args, **kwargs)
else:
# Decorator was applied to an instancemethod.
return wrapped(*args, **kwargs)
@args_as_ints
def funny_function(x, y, z=3):
"""Computes x*y + 2*z"""
return x * y + 2 * z
>>> funny_function(3, 4, z=5))
# 22
>>> help(funny_function)
Help on function funny_function in module __main__:
funny_function(x, y, z=3)
Computes x*y + 2*z
위에 jfs의 답변에서 언급한 바와 같이; 만약 당신이 외관상 서명에 관심이 있다면 (help,그리고.inspect.signature), 그 사용), 사용합니다functools.wraps완벽하게 괜찮습니다.
행동 측면에서 서명에 관심이 있는 경우(특히).TypeError일치하지 않는 ), 인수가 일치하지 않는 경우,functools.wraps보존하지 않습니다. 사하는것좋다니습이용다▁use니좋▁rather▁you습을 사용해야 합니다.decorator그것을 위해, 또는 그것의 핵심 엔진의 나의 일반화를 위해 , 라고 명명되었습니다.
from makefun import wraps
def args_as_ints(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("wrapper executes")
args = [int(x) for x in args]
kwargs = dict((k, int(v)) for k, v in kwargs.items())
return func(*args, **kwargs)
return wrapper
@args_as_ints
def funny_function(x, y, z=3):
"""Computes x*y + 2*z"""
return x*y + 2*z
print(funny_function("3", 4.0, z="5"))
# wrapper executes
# 22
help(funny_function)
# Help on function funny_function in module __main__:
#
# funny_function(x, y, z=3)
# Computes x*y + 2*z
funny_function(0)
# observe: no "wrapper executes" is printed! (with functools it would)
# TypeError: funny_function() takes at least 2 arguments (1 given)
에 대한 이 게시물도 참조하십시오.
from inspect import signature
def args_as_ints(f):
def g(*args, **kwargs):
args = [int(x) for x in args]
kwargs = dict((k, int(v)) for k, v in kwargs.items())
return f(*args, **kwargs)
sig = signature(f)
g.__signature__ = sig
g.__doc__ = f.__doc__
g.__annotations__ = f.__annotations__
g.__name__ = f.__name__
return g
@args_as_ints
def funny_function(x, y, z=3):
"""Computes x*y + 2*z"""
return x*y + 2*z
>>> funny_function("3", 4.0, z="5")
22
(이것이 구글에서 먼저 나타나기 때문에) 저는 그 대답을 추가하고 싶었습니다.검사 모듈은 기능의 서명을 가져와 장식기에 보존할 수 있습니다.하지만 그게 다가 아닙니다.서명을 수정하려는 경우 다음과 같이 수정할 수 있습니다.
from inspect import signature, Parameter, _ParameterKind
def foo(a: int, b: int) -> int:
return a + b
sig = signature(foo)
sig._parameters = dict(sig.parameters)
sig.parameters['c'] = Parameter(
'c', _ParameterKind.POSITIONAL_OR_KEYWORD,
annotation=int
)
foo.__signature__ = sig
>>> help(foo)
Help on function foo in module __main__:
foo(a: int, b: int, c: int) -> int
함수의 서명을 변경하려는 이유는 무엇입니까?
기능과 방법에 대한 적절한 문서를 작성하는 것이 가장 유용합니다.사중인경우를 *args, **kwargs구문을 입력한 다음 kwargs에서 다른 용도로 인수를 터뜨리면 해당 키워드 인수가 제대로 문서화되지 않으므로 함수의 서명을 수정할 수 있습니다.
def args_as_ints(f):
def g(*args, **kwargs):
args = [int(x) for x in args]
kwargs = dict((k, int(v)) for k, v in kwargs.items())
return f(*args, **kwargs)
g.__name__ = f.__name__
g.__doc__ = f.__doc__
return g
이름과 설명서를 수정합니다.기능 서명을 보존하기 위해,wrap는 와정히같위사용치다니됩서에은확▁at▁as다▁location와 정확히 위치에서 됩니다.g.__name__ = f.__name__, g.__doc__ = f.__doc__.
그wraps그 자체가 장식가입니다.우리는 클로저-내부 기능을 그 장식가에게 전달하고, 그것은 메타데이터를 고칠 것입니다.하지만 우리가 내부 기능만 넘겨준다면,wraps어디서 메타데이터를 복사해야 할지 모를 겁니다어떤 기능의 메타데이터를 보호해야 하는지 알아야 합니다.원래 기능을 알아야 합니다.
def args_as_ints(f):
def g(*args, **kwargs):
args = [int(x) for x in args]
kwargs = dict((k, int(v)) for k, v in kwargs.items())
return f(*args, **kwargs)
g=wraps(f)(g)
return g
wraps(f)필요한 함수를 반환할 것입니다.g매개 변수로 사용합니다.그리고 그것은 폐쇄를 반환하고 할당될 것입니다.g그런 다음 돌려드립니다.
언급URL : https://stackoverflow.com/questions/147816/preserving-signatures-of-decorated-functions
'codememo' 카테고리의 다른 글
| 셀 소모를 최소화하기 위한 배치 요청 (0) | 2023.08.01 |
|---|---|
| Javascript: 5의 다음 배수로 반올림합니다. (0) | 2023.08.01 |
| free()는 메모리를 제로로 만드는 것입니까? (0) | 2023.08.01 |
| Bottle Py: jQuery AJAX 요청에 대한 CORS 활성화 (0) | 2023.08.01 |
| 보기에 ID를 프로그래밍 방식으로 할당하려면 어떻게 해야 합니까? (0) | 2023.08.01 |