매개 변수와 함께 사용할 수있는 Python 데코레이터를 만들고 싶습니다.
@redirect_output("somewhere.log")
def foo():
....
또는 그것들없이 (예를 들어 출력을 기본적으로 stderr로 리디렉션) :
@redirect_output
def foo():
....
그게 가능합니까?
출력 리디렉션 문제에 대한 다른 솔루션을 찾고있는 것이 아니라 달성하고자하는 구문의 예일뿐입니다.
답변
이 질문이 오래되었다는 것을 알고 있지만 일부 의견은 새롭고 실행 가능한 모든 솔루션은 본질적으로 동일하지만 대부분은 매우 깨끗하거나 읽기 쉽지 않습니다.
thobe의 답변에서 말했듯이 두 경우를 모두 처리하는 유일한 방법은 두 시나리오를 모두 확인하는 것입니다. 가장 쉬운 방법은 단순히 단일 인수가 있고 그것이 callabe인지 확인하는 것입니다.
def decorator(*args, **kwargs):
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
# called as @decorator
else:
# called as @decorator(*args, **kwargs)
첫 번째 경우에는 일반 데코레이터가 수행하는 작업을 수행하고 전달 된 함수의 수정되거나 래핑 된 버전을 반환합니다.
두 번째 경우에는 * args, ** kwargs와 함께 전달 된 정보를 사용하는 ‘new’데코레이터를 반환합니다.
이것은 괜찮지 만 모든 데코레이터에 대해 작성해야하는 것은 꽤 성 가시고 깨끗하지 않을 수 있습니다. 대신, 데코레이터를 다시 작성할 필요없이 자동으로 데코레이터를 수정할 수 있으면 좋을 것입니다.하지만 이것이 데코레이터의 용도입니다!
다음 데코레이터 데코레이터를 사용하여 데코레이터를 할당 해제하여 인수가 있거나없는 데코레이터를 사용할 수 있습니다.
def doublewrap(f):
'''
a decorator decorator, allowing the decorator to be used as:
@decorator(with, arguments, and=kwargs)
or
@decorator
'''
@wraps(f)
def new_dec(*args, **kwargs):
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
# actual decorated function
return f(args[0])
else:
# decorator arguments
return lambda realf: f(realf, *args, **kwargs)
return new_dec
이제 @doublewrap을 사용하여 데코레이터를 데코레이터 할 수 있으며, 한 가지주의 사항과 함께 인수를 사용하거나 사용하지 않고 작동합니다.
위에서 언급했지만 여기서 반복해야합니다.이 데코레이터의 확인은 데코레이터가받을 수있는 인수 (즉, 호출 가능한 단일 인수를받을 수 없음)에 대한 가정을합니다. 현재 모든 발전기에 적용 할 수 있도록 만들고 있기 때문에 명심하거나 모순되는 경우 수정해야합니다.
다음은 그 사용법을 보여줍니다.
def test_doublewrap():
from util import doublewrap
from functools import wraps
@doublewrap
def mult(f, factor=2):
'''multiply a function's return value'''
@wraps(f)
def wrap(*args, **kwargs):
return factor*f(*args,**kwargs)
return wrap
# try normal
@mult
def f(x, y):
return x + y
# try args
@mult(3)
def f2(x, y):
return x*y
# try kwargs
@mult(factor=5)
def f3(x, y):
return x - y
assert f(2,3) == 10
assert f2(2,5) == 30
assert f3(8,1) == 5*7
답변
기본값 (kquinn에서 제안한대로)과 함께 키워드 인수를 사용하는 것은 좋은 생각이지만 괄호를 포함해야합니다.
@redirect_output()
def foo():
...
데코레이터에서 괄호없이 작동하는 버전을 원한다면 데코레이터 코드에서 두 시나리오를 모두 고려해야합니다.
Python 3.0을 사용하는 경우이를 위해 키워드 전용 인수를 사용할 수 있습니다.
def redirect_output(fn=None,*,destination=None):
destination = sys.stderr if destination is None else destination
def wrapper(*args, **kwargs):
... # your code here
if fn is None:
def decorator(fn):
return functools.update_wrapper(wrapper, fn)
return decorator
else:
return functools.update_wrapper(wrapper, fn)
Python 2.x에서는 varargs 트릭으로 에뮬레이션 할 수 있습니다.
def redirected_output(*fn,**options):
destination = options.pop('destination', sys.stderr)
if options:
raise TypeError("unsupported keyword arguments: %s" %
",".join(options.keys()))
def wrapper(*args, **kwargs):
... # your code here
if fn:
return functools.update_wrapper(wrapper, fn[0])
else:
def decorator(fn):
return functools.update_wrapper(wrapper, fn)
return decorator
이러한 버전을 사용하면 다음과 같은 코드를 작성할 수 있습니다.
@redirected_output
def foo():
...
@redirected_output(destination="somewhere.log")
def bar():
...
답변
나는 이것이 오래된 질문이라는 것을 알고 있지만 제안 된 기술이 정말 마음에 들지 않아 다른 방법을 추가하고 싶었습니다. django가 .NET의 login_required
데코레이터에서django.contrib.auth.decorators
정말 깨끗한 방법을 사용하는 것을 보았습니다 . 데코레이터의 문서 에서 볼 수 있듯이 단독으로 @login_required
또는 인수와 함께 사용할 수 있습니다 @login_required(redirect_field_name='my_redirect_field')
.
그들이하는 방식은 아주 간단합니다. 데코레이터 인수 앞에 kwarg
( function=None
)를 추가합니다 . 데코레이터가 단독으로 사용되면 데코레이터가 function
실제 함수가되고 인수로 호출 function
되면 None
.
예:
from functools import wraps
def custom_decorator(function=None, some_arg=None, some_other_arg=None):
def actual_decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
# Do stuff with args here...
if some_arg:
print(some_arg)
if some_other_arg:
print(some_other_arg)
return f(*args, **kwargs)
return wrapper
if function:
return actual_decorator(function)
return actual_decorator
@custom_decorator
def test1():
print('test1')
>>> test1()
test1
@custom_decorator(some_arg='hello')
def test2():
print('test2')
>>> test2()
hello
test2
@custom_decorator(some_arg='hello', some_other_arg='world')
def test3():
print('test3')
>>> test3()
hello
world
test3
django가 여기에서 제안한 다른 기술보다 더 우아하고 이해하기 쉬운 접근 방식을 사용합니다.
답변
예를 들어 첫 번째 인수의 유형을 사용하여 두 경우를 모두 감지하고 이에 따라 래퍼 (매개 변수없이 사용되는 경우) 또는 데코레이터 (인수와 함께 사용되는 경우)를 반환해야합니다.
from functools import wraps
import inspect
def redirect_output(fn_or_output):
def decorator(fn):
@wraps(fn)
def wrapper(*args, **args):
# Redirect output
try:
return fn(*args, **args)
finally:
# Restore output
return wrapper
if inspect.isfunction(fn_or_output):
# Called with no parameter
return decorator(fn_or_output)
else:
# Called with a parameter
return decorator
@redirect_output("output.log")
구문을 사용할 때는 redirect_output
단일 인수로 호출되며 인수 "output.log"
로 데코 레이팅 할 함수를 수락하는 데코레이터를 반환해야합니다. 로 사용되면 @redirect_output
데코 레이트 할 함수를 인수로 사용하여 직접 호출됩니다.
즉, @
구문 뒤에는 데코레이션 할 함수를 유일한 인수로 받아들이고 데코 레이팅 된 함수를 반환하는 함수가 결과 인 표현식이 따라야합니다. 표현식 자체는 함수 호출이 될 수 있습니다 @redirect_output("output.log")
. 복잡하지만 사실 🙂
답변
여기에 몇 가지 답변이 이미 문제를 잘 해결했습니다. 그러나 스타일과 관련하여 functools.partial
David Beazley의 Python Cookbook 3 에서 제안한대로를 사용하여이 데코레이터 곤경을 해결하는 것을 선호합니다 .
from functools import partial, wraps
def decorator(func=None, foo='spam'):
if func is None:
return partial(decorator, foo=foo)
@wraps(func)
def wrapper(*args, **kwargs):
# do something with `func` and `foo`, if you're so inclined
pass
return wrapper
예, 당신은 할 수 있습니다
@decorator()
def f(*args, **kwargs):
pass
펑키 한 해결 방법없이 이상하게 보였고 @decorator
.
2 차 미션 목표와 관련하여 함수의 출력을 리디렉션하는 것은이 스택 오버플로 게시물 에서 다룹니다 .
더 자세히 알고 싶다면 온라인에서 무료로 읽을 수 있는 Python Cookbook 3의 Chapter 9 (Metaprogramming)을 확인하세요 .
그 자료 중 일부는 Beazley의 멋진 YouTube 동영상 Python 3 Metaprogramming 에서 라이브 데모 (더 많은 것!)됩니다 .
해피 코딩 🙂
답변
파이썬 데코레이터는 인수를 제공하는지 여부에 따라 근본적으로 다른 방식으로 호출됩니다. 장식은 실제로 (구문 적으로 제한된) 표현 일뿐입니다.
첫 번째 예에서 :
@redirect_output("somewhere.log")
def foo():
....
함수 redirect_output
는 주어진 인수로 호출되며, 데코레이터 함수를 반환 할 것으로 예상되며, 이는 자체적으로 foo
인수로 호출되며 (마지막으로!) 최종 데코 레이팅 된 함수를 반환 할 것으로 예상됩니다.
동등한 코드는 다음과 같습니다.
def foo():
....
d = redirect_output("somewhere.log")
foo = d(foo)
두 번째 예제에 해당하는 코드는 다음과 같습니다.
def foo():
....
d = redirect_output
foo = d(foo)
따라서 원하는 것을 할 수 있지만 완전히 매끄럽지 않게 할 수 있습니다 .
import types
def redirect_output(arg):
def decorator(file, f):
def df(*args, **kwargs):
print 'redirecting to ', file
return f(*args, **kwargs)
return df
if type(arg) is types.FunctionType:
return decorator(sys.stderr, arg)
return lambda f: decorator(arg, f)
데코레이터에 대한 인수로 함수를 사용하려는 경우가 아니면 데코레이터가 인수가 없다고 잘못 가정하지 않는 한 괜찮습니다. 이 장식이 함수 유형을 반환하지 않는 다른 장식에 적용되는 경우에도 실패합니다.
대체 방법은 데코레이터 함수가 인수가없는 경우에도 항상 호출되도록 요구하는 것입니다. 이 경우 두 번째 예는 다음과 같습니다.
@redirect_output()
def foo():
....
데코레이터 함수 코드는 다음과 같습니다.
def redirect_output(file = sys.stderr):
def decorator(file, f):
def df(*args, **kwargs):
print 'redirecting to ', file
return f(*args, **kwargs)
return df
return lambda f: decorator(file, f)
답변
실제로 @ bj0 솔루션의 경고 사례는 쉽게 확인할 수 있습니다.
def meta_wrap(decor):
@functools.wraps(decor)
def new_decor(*args, **kwargs):
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
# this is the double-decorated f.
# Its first argument should not be a callable
doubled_f = decor(args[0])
@functools.wraps(doubled_f)
def checked_doubled_f(*f_args, **f_kwargs):
if callable(f_args[0]):
raise ValueError('meta_wrap failure: '
'first positional argument cannot be callable.')
return doubled_f(*f_args, **f_kwargs)
return checked_doubled_f
else:
# decorator arguments
return lambda real_f: decor(real_f, *args, **kwargs)
return new_decor
이 오류 방지 버전의 meta_wrap
.
@meta_wrap
def baddecor(f, caller=lambda x: -1*x):
@functools.wraps(f)
def _f(*args, **kwargs):
return caller(f(args[0]))
return _f
@baddecor # used without arg: no problem
def f_call1(x):
return x + 1
assert f_call1(5) == -6
@baddecor(lambda x : 2*x) # bad case
def f_call2(x):
return x + 1
f_call2(5) # raises ValueError
# explicit keyword: no problem
@baddecor(caller=lambda x : 100*x)
def f_call3(x):
return x + 1
assert f_call3(5) == 600
