매개 변수가있는 데코레이터?


401

데코레이터가 변수 'insurance_mode'를 전송하는 데 문제가 있습니다. 나는 다음 데코레이터 문장으로 할 것입니다 :

 @execute_complete_reservation(True)
 def test_booking_gta_object(self):
     self.test_select_gta_object()

그러나 불행히도이 진술은 효과가 없습니다. 아마도이 문제를 해결하는 더 좋은 방법이있을 것입니다.

def execute_complete_reservation(test_case,insurance_mode):
    def inner_function(self,*args,**kwargs):
        self.test_create_qsf_query()
        test_case(self,*args,**kwargs)
        self.test_select_room_option()
        if insurance_mode:
            self.test_accept_insurance_crosseling()
        else:
            self.test_decline_insurance_crosseling()
        self.test_configure_pax_details()
        self.test_configure_payer_details

    return inner_function

3
귀하의 예는 구문 상 유효하지 않습니다. execute_complete_reservation두 개의 매개 변수를 사용하지만 하나를 전달합니다. 데코레이터는 다른 함수 안에 함수를 감싸는 구문 설탕입니다. 전체 설명서는 docs.python.org/reference/compound_stmts.html#function 을 참조하십시오 .
Brian Clapper

답변:


687

인수가있는 데코레이터 구문은 약간 다릅니다. 인수가있는 데코레이터는 함수를 가져 오고 다른 함수를 반환 하는 함수 를 반환해야합니다. 따라서 실제로 정상적인 데코레이터를 반환해야합니다. 약간 혼란스럽지 않습니까? 내가 말하고 싶은 건:

def decorator_factory(argument):
    def decorator(function):
        def wrapper(*args, **kwargs):
            funny_stuff()
            something_with_argument(argument)
            result = function(*args, **kwargs)
            more_funny_stuff()
            return result
        return wrapper
    return decorator

여기서 당신은 주제에 대해 더 많은 것을 읽을 수 있습니다-호출 가능한 객체를 사용하여 이것을 구현하는 것도 가능합니다.


56
GVR이 '함수'뒤에 후속 데코레이터 인수로 매개 변수를 전달하여 왜 그것을 구현하지 않았는지 궁금합니다. '요 다우 그, 당신이 클로저를 좋아한다고 들었습니다 ...'등.
Michel Müller

3
> 함수가 첫 번째 인수입니까 아니면 마지막입니까? 매개 변수가 가변 길이의 매개 변수 목록이기 때문에 분명히 먼저. > 정의에있는 것과 다른 서명으로 함수를 "호출"하는 것도 이상합니다. 지적했듯이 실제로 실제로 잘 맞을 것입니다. 클래스 메서드가 호출되는 방식과 거의 유사합니다. 더 명확하게하기 위해 decorator (self_func, param1, ...) 규칙과 같은 것을 가질 수 있습니다. 그러나 주 : 나는 여기서 어떤 변화를 옹호하지 않고있다. 파이썬은 그것을위한 길을 너무 멀어 우리는 변화가 어떻게 진행되었는지를 볼 수있다.
Michel Müller

21
당신은 매우 유용합니다 :) 래퍼를 장식하는 functools.wraps 잊어
socketpair의를

10
함수를 호출 할 때 리턴을 잊어 버렸습니다. 예 : return function(*args, **kwargs)
formiaczek

36
선택의 여지가있는 경우에도이 데코레이터를 사용할 필요는 @decorator()없습니다 @decorator.
Patrick Mevzek

325

편집 : 데코레이터의 정신 모델에 대한 심층적 인 이해를 위해이 멋진 Pycon Talk를 살펴보십시오 . 30 분의 가치가 있습니다.

논쟁이있는 데코레이터에 대해 생각하는 한 가지 방법은

@decorator
def foo(*args, **kwargs):
    pass

로 번역

foo = decorator(foo)

데코레이터가 논쟁을한다면

@decorator_with_args(arg)
def foo(*args, **kwargs):
    pass

로 번역

foo = decorator_with_args(arg)(foo)

decorator_with_args 사용자 정의 인수를 허용하고 실제 데코레이터를 리턴하는 함수입니다 (데코 레이팅 된 함수에 적용됨).

데코레이터를 쉽게 만들기 위해 부분적으로 간단한 트릭을 사용합니다.

from functools import partial

def _pseudo_decor(fun, argument):
    def ret_fun(*args, **kwargs):
        #do stuff here, for eg.
        print ("decorator arg is %s" % str(argument))
        return fun(*args, **kwargs)
    return ret_fun

real_decorator = partial(_pseudo_decor, argument=arg)

@real_decorator
def foo(*args, **kwargs):
    pass

최신 정보:

이상이 foo됩니다real_decorator(foo)

함수를 꾸미는 한 가지 효과는 foo데코레이터 선언시 이름 이 무시 된다는 것 입니다. foo에 의해 반환되는 모든 것에 의해 "재정의"됩니다 real_decorator. 이 경우 새로운 함수 객체입니다.

의 모든 foo메타 데이터, 특히 docstring 및 함수 이름이 재정의됩니다.

>>> print(foo)
<function _pseudo_decor.<locals>.ret_fun at 0x10666a2f0>

functools.wraps 는 docstring과 "name"을 반환 된 함수로 "리프트"하는 편리한 방법을 제공합니다.

from functools import partial, wraps

def _pseudo_decor(fun, argument):
    # magic sauce to lift the name and doc of the function
    @wraps(fun)
    def ret_fun(*args, **kwargs):
        #do stuff here, for eg.
        print ("decorator arg is %s" % str(argument))
        return fun(*args, **kwargs)
    return ret_fun

real_decorator = partial(_pseudo_decor, argument=arg)

@real_decorator
def bar(*args, **kwargs):
    pass

>>> print(bar)
<function __main__.bar(*args, **kwargs)>

4
귀하의 답변은 데코레이터의 고유 한 직교성을 완벽하게 설명했습니다. 감사합니다
zsf222

추가 할 수 @functools.wraps있습니까?
Mr_and_Mrs_D

1
@Mr_and_Mrs_D, 예를 사용하여 게시물을 업데이트했습니다 functool.wraps. 예제에서 추가하면 독자를 더 혼란스럽게 할 수 있습니다.
srj

7
arg여기 가 뭐야 !?
표시 이름

1
전달 된 인수 bar를의 인수에 real_decorator어떻게 전달 하시겠습니까?
창 자오

85

IMHO가 매우 우아한 아이디어를 보여주고 싶습니다. t.dubrownik이 제안한 솔루션은 항상 동일한 패턴을 보여줍니다. 데코레이터가하는 일에 관계없이 3 층 래퍼가 필요합니다.

그래서 나는 이것이 메타 데코레이터, 즉 데코레이터를위한 데코레이터의 직업이라고 생각했습니다. 데코레이터는 함수이므로 실제로 인수를 사용하여 일반 데코레이터로 작동합니다.

def parametrized(dec):
    def layer(*args, **kwargs):
        def repl(f):
            return dec(f, *args, **kwargs)
        return repl
    return layer

매개 변수를 추가하기 위해 일반 데코레이터에 적용 할 수 있습니다. 예를 들어 함수의 결과를 두 배로 만드는 데코레이터가 있다고 가정 해보십시오.

def double(f):
    def aux(*xs, **kws):
        return 2 * f(*xs, **kws)
    return aux

@double
def function(a):
    return 10 + a

print function(3)    # Prints 26, namely 2 * (10 + 3)

함께 @parametrized우리는 일반적인 구축 할 수 있습니다 @multiply매개 변수를 가진 장식을

@parametrized
def multiply(f, n):
    def aux(*xs, **kws):
        return n * f(*xs, **kws)
    return aux

@multiply(2)
def function(a):
    return 10 + a

print function(3)    # Prints 26

@multiply(3)
def function_again(a):
    return 10 + a

print function(3)          # Keeps printing 26
print function_again(3)    # Prints 39, namely 3 * (10 + 3)

일반적으로 매개 변수화 된 데코레이터 의 첫 번째 매개 변수 는 함수이고 나머지 인수는 매개 변수화 된 데코레이터의 매개 변수에 해당합니다.

흥미로운 사용법 예제는 형식 안전 독단적 데코레이터입니다.

import itertools as it

@parametrized
def types(f, *types):
    def rep(*args):
        for a, t, n in zip(args, types, it.count()):
            if type(a) is not t:
                raise TypeError('Value %d has not type %s. %s instead' %
                    (n, t, type(a))
                )
        return f(*args)
    return rep

@types(str, int)  # arg1 is str, arg2 is int
def string_multiply(text, times):
    return text * times

print(string_multiply('hello', 3))    # Prints hellohellohello
print(string_multiply(3, 3))          # Fails miserably with TypeError

마지막 참고 사항 : functools.wraps래퍼 함수 에는 사용하지 않지만 항상 사용하는 것이 좋습니다.


3
이것을 정확하게 사용하지는 않았지만 컨셉에 대한 내 머리를 얻는 데 도움이되었습니다. :) 감사합니다!
mouckatron

나는 이것을 시도하고 몇 가지 문제 가 있었다 .
Jeff

@Jeff 당신이 가진 문제의 종류를 우리와 공유 할 수 있습니까?
Dacav

나는 내 질문에 그것을 연결시키고 그것을 알아 냈습니다 ... 나는 @wraps나의 특별한 경우를 위해 내 전화 를 해야했습니다 .
Jeff

4
오 소년, 나는 이것에 하루 종일 잃었다. 고맙게도, 나는 이 답변을 보았습니다 (실수로 전체 인터넷에서 만든 최고의 답변 일 수 있음). 그들도 당신의 @parametrized트릭을 사용합니다 . 내가 가진 문제는 @구문 이 실제 호출같다는 것을 잊었다는 것입니다 (어쨌든 나는 그것을 알고 있었고 당신이 내 질문에서 수집 할 수있는 것과 동시에 그것을 알지 못했습니다). 따라서 @구문을 평범한 호출 로 변환 하여 작동 방식을 확인 하려는 경우 먼저 구문을 주석 처리하거나 두 번 호출하여 mumbojumbo 결과를 얻는 것이 좋습니다.
z33k

79

다음은 t.dubrownik 's answer의 약간 수정 된 버전입니다 . 왜?

  1. 일반적인 템플릿으로 원래 함수에서 반환 값을 반환해야합니다.
  2. 이것은 다른 데코레이터 / 코드에 영향을 줄 수있는 함수의 이름을 변경합니다.

그래서 사용하십시오 @functools.wraps():

from functools import wraps

def decorator(argument):
    def real_decorator(function):
        @wraps(function)
        def wrapper(*args, **kwargs):
            funny_stuff()
            something_with_argument(argument)
            retval = function(*args, **kwargs)
            more_funny_stuff()
            return retval
        return wrapper
    return real_decorator

37

나는 당신의 문제가 데코레이터에게 인수를 전달한다고 가정합니다. 이것은 약간 까다 롭고 간단하지 않습니다.

이를 수행하는 방법의 예는 다음과 같습니다.

class MyDec(object):
    def __init__(self,flag):
        self.flag = flag
    def __call__(self, original_func):
        decorator_self = self
        def wrappee( *args, **kwargs):
            print 'in decorator before wrapee with flag ',decorator_self.flag
            original_func(*args,**kwargs)
            print 'in decorator after wrapee with flag ',decorator_self.flag
        return wrappee

@MyDec('foo de fa fa')
def bar(a,b,c):
    print 'in bar',a,b,c

bar('x','y','z')

인쇄물:

in decorator before wrapee with flag  foo de fa fa
in bar x y z
in decorator after wrapee with flag  foo de fa fa

자세한 내용은 Bruce Eckel의 기사를 참조하십시오.


20
데코레이터 클래스를 조심하십시오. 인스턴스 메소드 디스크립터의 논리를 수동으로 다시 작성하지 않으면 메소드에서 작동하지 않습니다.

9
델난, 정교하게 신경써? 이 패턴을 한 번만 사용해야했기 때문에 아직 함정에 부딪치지 않았습니다.
로스 로저스

2
@RossRogers 내 추측은 @delnan이 __name__데코레이터 클래스의 인스턴스가 가지고 있지 않은 것을 말하는 것 입니까?
jamesc

9
@jamesc 그것도 비교적 해결하기 쉽지만. 내가 언급 한 특정 사례 는 바인딩 된 방법이 아니며 자동으로 전달되지 class Foo: @MyDec(...) def method(self, ...): blah않기 때문에 작동하지 않는 것 입니다. 설명자를 만들고에 바운드 메서드를 만들어서이 문제를 해결할 수도 있지만 더 복잡하고 덜 명확합니다. 결국 데코레이터 클래스는 생각만큼 편리하지 않습니다. Foo().methodselfMyDec__get__

2
@delnan이 경고가 더 두드러지게 나타나기를 바랍니다. 나는 그것을 명중하고 작동하는 솔루션을 보는 데 관심이 있습니다.
HaPsantran

12
def decorator(argument):
    def real_decorator(function):
        def wrapper(*args):
            for arg in args:
                assert type(arg)==int,f'{arg} is not an interger'
            result = function(*args)
            result = result*argument
            return result
        return wrapper
    return real_decorator

데코레이터 사용법

@decorator(2)
def adder(*args):
    sum=0
    for i in args:
        sum+=i
    return sum

그런 다음

adder(2,3)

생산

10

그러나

adder('hi',3)

생산

---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-143-242a8feb1cc4> in <module>
----> 1 adder('hi',3)

<ipython-input-140-d3420c248ebd> in wrapper(*args)
      3         def wrapper(*args):
      4             for arg in args:
----> 5                 assert type(arg)==int,f'{arg} is not an interger'
      6             result = function(*args)
      7             result = result*argument

AssertionError: hi is not an interger

8

이것은 ()매개 변수 를 제공하지 않아도 필요하지 않은 함수 데코레이터 용 템플리트입니다 .

import functools


def decorator(x_or_func=None, *decorator_args, **decorator_kws):
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kws):
            if 'x_or_func' not in locals() \
                    or callable(x_or_func) \
                    or x_or_func is None:
                x = ...  # <-- default `x` value
            else:
                x = x_or_func
            return func(*args, **kws)

        return wrapper

    return _decorator(x_or_func) if callable(x_or_func) else _decorator

이에 대한 예는 다음과 같습니다.

def multiplying(factor_or_func=None):
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            if 'factor_or_func' not in locals() \
                    or callable(factor_or_func) \
                    or factor_or_func is None:
                factor = 1
            else:
                factor = factor_or_func
            return factor * func(*args, **kwargs)
        return wrapper
    return _decorator(factor_or_func) if callable(factor_or_func) else _decorator


@multiplying
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying()
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying(10)
def summing(x): return sum(x)

print(summing(range(10)))
# 450

있음을 유의하십시오 factor_or_func(또는 다른 매개 변수) 재 할당 않겠다 안wrapper().
norok2

왜 체크인해야 locals()합니까?
시탈 샤

@ShitalShah는 데코레이터를 사용하지 않은 경우를 다룹니다 ().
norok2

4

필자의 경우, 새로운 데코레이터 함수를 만들기 위해 한 줄 람다를 통해이 문제를 해결하기로 결정했습니다.

def finished_message(function, message="Finished!"):

    def wrapper(*args, **kwargs):
        output = function(*args,**kwargs)
        print(message)
        return output

    return wrapper

@finished_message
def func():
    pass

my_finished_message = lambda f: finished_message(f, "All Done!")

@my_finished_message
def my_func():
    pass

if __name__ == '__main__':
    func()
    my_func()

실행되면 다음이 인쇄됩니다.

Finished!
All Done!

아마도 다른 솔루션만큼 확장 가능하지는 않지만 나를 위해 일했습니다.


작동합니다. 그래도 값을 데코레이터로 설정하기가 어렵습니다.
Arindam Roychowdhury

3

파이썬은이 두 경우에서 완전히 다른 행동을 기대하기 때문에 매개 변수를 사용하거나 사용하지 않는 데코레이터를 작성하는 것은 어려운 일입니다! 많은 답변 이이 문제를 해결하려고 시도했으며 아래는 @ norok2의 답변 개선입니다. 특히이 변형은locals() .

@ norok2가 제공 한 것과 동일한 예를 따르면 :

import functools

def multiplying(f_py=None, factor=1):
    assert callable(f_py) or f_py is None
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            return factor * func(*args, **kwargs)
        return wrapper
    return _decorator(f_py) if callable(f_py) else _decorator


@multiplying
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying()
def summing(x): return sum(x)

print(summing(range(10)))
# 45


@multiplying(factor=10)
def summing(x): return sum(x)

print(summing(range(10)))
# 450

이 코드를 가지고 놀아 라 .

캐치 사용자는 위치 매개 변수 대신 키, 값 쌍의 매개 변수를 제공해야하며 첫 번째 매개 변수는 예약되어 있습니다.


2

다음 두 코드는 거의 동일하다는 것이 잘 알려져 있습니다.

@dec
def foo():
    pass    foo = dec(foo)

############################################
foo = dec(foo)

가장 일반적인 실수는 @단순히 가장 왼쪽의 주장을 숨기는 것이라고 생각하는 것입니다 .

@dec(1, 2, 3)
def foo():
    pass    
###########################################
foo = dec(foo, 1, 2, 3)

위의 방법이 효과적이라면 데코레이터를 작성하는 것이 훨씬 쉬울 것 @입니다. 불행히도, 그것은 일이 끝나는 방식이 아닙니다.


Wait몇 초 동안 프로그램 실행을 방해 하는 데코레이터 를 생각해보십시오 . 대기 시간을 전달하지 않으면 기본값은 1 초입니다. 사용 사례는 다음과 같습니다.

##################################################
@Wait
def print_something(something):
    print(something)

##################################################
@Wait(3)
def print_something_else(something_else):
    print(something_else)

##################################################
@Wait(delay=3)
def print_something_else(something_else):
    print(something_else)

Wait같은 인수,이 @Wait(3)다음 호출이 Wait(3) 실행 되기 전에 다른 어떤 일이 발생합니다.

즉, 다음 두 코드는 동일합니다.

@Wait(3)
def print_something_else(something_else):
    print(something_else)

###############################################
return_value = Wait(3)
@return_value
def print_something_else(something_else):
    print(something_else)

이것은 문제입니다.

if `Wait` has no arguments:
    `Wait` is the decorator.
else: # `Wait` receives arguments
    `Wait` is not the decorator itself.
    Instead, `Wait` ***returns*** the decorator

하나의 솔루션이 아래에 나와 있습니다.

다음과 같은 클래스를 만들어 보자 DelayedDecorator.

class DelayedDecorator:
    def __init__(i, cls, *args, **kwargs):
        print("Delayed Decorator __init__", cls, args, kwargs)
        i._cls = cls
        i._args = args
        i._kwargs = kwargs
    def __call__(i, func):
        print("Delayed Decorator __call__", func)
        if not (callable(func)):
            import io
            with io.StringIO() as ss:
                print(
                    "If only one input, input must be callable",
                    "Instead, received:",
                    repr(func),
                    sep="\n",
                    file=ss
                )
                msg = ss.getvalue()
            raise TypeError(msg)
        return i._cls(func, *i._args, **i._kwargs)

이제 다음과 같이 쓸 수 있습니다 :

 dec = DelayedDecorator(Wait, delay=4)
 @dec
 def delayed_print(something):
    print(something)

참고 :

  • dec 여러 인수를 허용하지 않습니다.
  • dec 랩핑 된 함수 만 허용합니다.

    import inspect 클래스 PolyArgDecoratorMeta (type) : def call (Wait, * args, ** kwargs) : try : arg_count = len (args) if (arg_count == 1) : if callable (args [0]) : SuperClass = inspect. getmro (PolyArgDecoratorMeta) [1] r = 수퍼 클래스. 요구 (기다린 인수 [0]) 또, R = DelayedDecorator (기다린 인수 *, ** kwargs로) 다른 마지막으로 R = DelayedDecorator (기다린 인수 *, ** kwargs로) 통과 복귀 R

    가져 오기 시간 클래스 Wait (metaclass = PolyArgDecoratorMeta) : def init (i, func, delay = 2) : i._func = func i._delay = delay

    def __call__(i, *args, **kwargs):
        time.sleep(i._delay)
        r = i._func(*args, **kwargs)
        return r 

다음 두 코드는 동일합니다.

@Wait
def print_something(something):
     print (something)

##################################################

def print_something(something):
    print(something)
print_something = Wait(print_something)

"something"다음과 같이 콘솔에 매우 느리게 인쇄 할 수 있습니다 .

print_something("something")

#################################################
@Wait(delay=1)
def print_something_else(something_else):
    print(something_else)

##################################################
def print_something_else(something_else):
    print(something_else)

dd = DelayedDecorator(Wait, delay=1)
print_something_else = dd(print_something_else)

##################################################

print_something_else("something")

최종 노트

이 코드를 많이 보일 수도 있지만, 당신은 클래스를 작성하지 않아도 DelayedDecoratorPolyArgDecoratorMeta모든 시간을. 다음과 같이 개인적으로 작성해야하는 유일한 코드는 상당히 짧습니다.

from PolyArgDecoratorMeta import PolyArgDecoratorMeta
import time
class Wait(metaclass=PolyArgDecoratorMeta):
 def __init__(i, func, delay = 2):
     i._func = func
     i._delay = delay

 def __call__(i, *args, **kwargs):
     time.sleep(i._delay)
     r = i._func(*args, **kwargs)
     return r

1

이 데코레이터 함수를 정의하여 사용자 정의 데코레이터 함수를 생성하십시오.

def decoratorize(FUN, **kw):
    def foo(*args, **kws):
        return FUN(*args, **kws, **kw)
    return foo

이 방법으로 사용하십시오 :

    @decoratorize(FUN, arg1 = , arg2 = , ...)
    def bar(...):
        ...

1

위의 큰 답변. 이것도 @wraps원래 함수에서 doc 문자열과 함수 이름을 가져 와서 새로운 랩핑 된 버전에 적용합니다.

from functools import wraps

def decorator_func_with_args(arg1, arg2):
    def decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            print("Before orginal function with decorator args:", arg1, arg2)
            result = f(*args, **kwargs)
            print("Ran after the orginal function")
            return result
        return wrapper
    return decorator

@decorator_func_with_args("foo", "bar")
def hello(name):
    """A function which prints a greeting to the name provided.
    """
    print('hello ', name)
    return 42

print("Starting script..")
x = hello('Bob')
print("The value of x is:", x)
print("The wrapped functions docstring is:", hello.__doc__)
print("The wrapped functions name is:", hello.__name__)

인쇄물:

Starting script..
Before orginal function with decorator args: foo bar
hello  Bob
Ran after the orginal function
The value of x is: 42
The wrapped functions docstring is: A function which prints a greeting to the name provided.
The wrapped functions name is: hello

0

함수와 데코레이터가 모두 인수를 취해야하는 경우 아래 접근법을 따를 수 있습니다.

예를 들어 decorator1인수를 취하는 데코레이터 가 있습니다.

@decorator1(5)
def func1(arg1, arg2):
    print (arg1, arg2)

func1(1, 2)

이제 decorator1인수가 동적이거나 함수를 호출하는 동안 전달되어야하는 경우

def func1(arg1, arg2):
    print (arg1, arg2)


a = 1
b = 2
seconds = 10

decorator1(seconds)(func1)(a, b)

위 코드에서

  • seconds 의 주장이다 decorator1
  • a, b 의 주장이다 func1
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.