codememo

장식된 기능의 서명 보존

tipmemo 2023. 8. 1. 20:33
반응형

장식된 기능의 서명 보존

제가 매우 일반적인 것을 하는 장식가를 썼다고 가정해 보겠습니다.예를 들어, 모든 인수를 특정 유형으로 변환하고, 로깅을 수행하고, 메모화를 구현하는 등의 작업을 수행할 수 있습니다.

다음은 예입니다.

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과 함께 어떻게 장식된 기능을 구성해야 합니까?

다른 접근법은?

  1. 데코레이터 모듈 설치:

    $ pip install decorator
    
  2. 정의를 합니다.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, **kwargsx, 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함수 - 이제 수정되었습니다.

데코레이터 모듈, 특히 이 문제를 해결하는 데코레이터 모듈을 살펴봅니다.

두 번째 옵션:

  1. 랩 모듈 설치:

이지_설치 랩

랩은 보너스가 있고, 클래스 서명을 유지합니다.


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

반응형