파이썬 함수에서 데코레이터를 제거하는 방법
다음이 있다고 가정 해 보겠습니다.
def with_connection(f):
def decorated(*args, **kwargs):
f(get_connection(...), *args, **kwargs)
return decorated
@with_connection
def spam(connection):
# Do something
spam
연결을 설정하는 번거 로움 (또는 데코레이터가 수행하는 작업)을 거치지 않고 함수 를 테스트하고 싶습니다 .
주어진 spam
, 데코레이터에서 데코레이터를 제거하고 기본 "비 데코레이션"기능을 얻는 방법은 무엇입니까?
일반적인 경우에는 할 수 없습니다.
@with_connection
def spam(connection):
# Do something
다음과 같다
def spam(connection):
# Do something
spam = with_connection(spam)
이는 "원본"스팸이 더 이상 존재하지 않을 수도 있음을 의미합니다. (너무 예쁘지 않은) 해킹은 다음과 같습니다.
def with_connection(f):
def decorated(*args, **kwargs):
f(get_connection(...), *args, **kwargs)
decorated._original = f
return decorated
@with_connection
def spam(connection):
# Do something
spam._original(testcon) # calls the undecorated function
이 질문에 대한 약간의 업데이트가 있습니다. Python 3을 사용하는 경우 __wrapped__
stdlib의 데코레이터에 대한 속성을 사용할 수 있습니다 .
다음은 Python Cookbook, 3 판, 섹션 9.3 데코레이터 풀기의 예입니다.
>>> @somedecorator
>>> def add(x, y):
... return x + y
...
>>> orig_add = add.__wrapped__
>>> orig_add(3, 4)
7
>>>
커스텀 데코레이터에서 함수를 언 래핑하려는 경우 데코레이터 함수 는 Python Cookbook, 3rd edition, section 9.2에서 데코레이터 작성시 함수 메타 데이터 보존의 함수 를 사용해야 wraps
합니다.functools
>>> from functools import wraps
>>> def somedecoarator(func):
... @wraps(func)
... def wrapper(*args, **kwargs):
... # decorator implementation here
... # ...
... return func(*args, kwargs)
...
... return wrapper
이 메타 데코레이터를 사용하면 balpha의 솔루션을보다 일반화 할 수 있습니다.
def include_original(dec):
def meta_decorator(f):
decorated = dec(f)
decorated._original = f
return decorated
return meta_decorator
그런 다음 @include_original을 사용하여 데코레이터를 장식 할 수 있으며 모든 데코레이터에는 테스트 가능한 (데코레이션되지 않은) 버전이 내부에 숨겨져 있습니다.
@include_original
def shout(f):
def _():
string = f()
return string.upper()
return _
@shout
def function():
return "hello world"
>>> print function()
HELLO_WORLD
>>> print function._original()
hello world
보라, FuglyHackThatWillWorkForYourExampleButICantPromiseAnythingElse :
orig_spam = spam.func_closure[0].cell_contents
편집 : 두 번 이상 장식 된 함수 / 방법과 더 복잡한 데코레이터의 경우 다음 코드를 사용해 볼 수 있습니다. 데코 레이팅 된 함수는 원래 함수와 다르게 __name__d라는 사실에 의존합니다.
def search_for_orig(decorated, orig_name):
for obj in (c.cell_contents for c in decorated.__closure__):
if hasattr(obj, "__name__") and obj.__name__ == orig_name:
return obj
if hasattr(obj, "__closure__") and obj.__closure__:
found = search_for_orig(obj, orig_name)
if found:
return found
return None
>>> search_for_orig(spam, "spam")
<function spam at 0x027ACD70>
그래도 바보 같은 증거는 아닙니다. 데코레이터에서 반환 된 함수의 이름이 데코 레이팅 된 함수와 같으면 실패합니다. hasattr () 검사의 순서도 휴리스틱이며 어떤 경우에도 잘못된 결과를 반환하는 장식 체인이 있습니다.
이제 장식되지 않은 패키지를 사용할 수 있습니다 .
>>> from undecorated import undecorated
>>> undecorated(spam)
바닥 기능에 도달 할 때까지 다른 데코레이터의 모든 레이어를 파헤치는 번거 로움을 겪고 원래 데코레이터를 변경할 필요가 없습니다. Python 2와 Python 3 모두에서 작동합니다.
하는 대신 ...
def with_connection(f):
def decorated(*args, **kwargs):
f(get_connection(...), *args, **kwargs)
return decorated
@with_connection
def spam(connection):
# Do something
orig_spam = magic_hack_of_a_function(spam)
당신은 할 수 있습니다 ...
def with_connection(f):
...
def spam_f(connection):
...
spam = with_connection(spam_f)
... 이것은 모든 @decorator
구문이 수행 하는 것입니다 spam_f
. 그러면 원본에 정상적으로 액세스 할 수 있습니다 .
The usual approach to testing such functions is to make any dependencies, such as get_connection, configurable. Then you can override it with a mock while testing. Basically the same as dependency injection in the Java world but a lot simpler thanks to Pythons dynamic nature.
Code for it might look something like this:
# decorator definition
def with_connection(f):
def decorated(*args, **kwargs):
f(with_connection.connection_getter(), *args, **kwargs)
return decorated
# normal configuration
with_connection.connection_getter = lambda: get_connection(...)
# inside testsuite setup override it
with_connection.connection_getter = lambda: "a mock connection"
Depending on your code you could find a better object than the decorator to stick the factory function on. The issue with having it on the decorator is that you'd have to remember to restore it to the old value in the teardown method.
It's good practice to decorate decorators with functools.wraps
like so:
import functools
def with_connection(f):
@functools.wraps(f)
def decorated(*args, **kwargs):
f(get_connection(...), *args, **kwargs)
return decorated
@with_connection
def spam(connection):
# Do something
As of Python 3.2, this will automatically add a __wrapped__
attribute that lets you retrieve the original, undecorated function:
>>> spam.__wrapped__
<function spam at 0x7fe4e6dfc048>
However, instead of manually accessing the __wrapped__
attribute, it's better to use inspect.unwrap
:
>>> inspect.unwrap(spam)
<function spam at 0x7fe4e6dfc048>
the original function is stored in spam.__closure__[0].cell_contents
.
Decorator uses closure to bind original function with extra layer of functionality. The original function must be stored in a closure cell kept by one of the functions in the nested structure of decorator.
Example:
>>> def add(f):
... def _decorator(*args, **kargs):
... print('hello_world')
... return f(*args, **kargs)
... return _decorator
...
>>> @add
... def f(msg):
... print('f ==>', msg)
...
>>> f('alice')
hello_world
f ==> alice
>>> f.__closure__[0].cell_contents
<function f at 0x7f5d205991e0>
>>> f.__closure__[0].cell_contents('alice')
f ==> alice
this is the core principle of undecorated, you could refer to the source code for more details.
Add a do-nothing decorator:
def do_nothing(f):
return f
After defining or importing with_connection but before you get to the methods that use it as a decorator, add:
if TESTING:
with_connection = do_nothing
Then if you set the global TESTING to True, you will have replaced with_connection with a do-nothing decorator.
참고URL : https://stackoverflow.com/questions/1166118/how-to-strip-decorators-from-a-function-in-python
'Program Tip' 카테고리의 다른 글
사용자 정의 유형으로 std :: set, 중복이 없는지 확인하는 방법 (0) | 2020.12.10 |
---|---|
헤더 파일의 변수 선언 (0) | 2020.12.10 |
int 변수를 double로 변환해야합니다. (0) | 2020.12.10 |
Android SQLite 삽입 또는 업데이트 (0) | 2020.12.10 |
matplotlib Python에서 다른 막대 색상 설정 (0) | 2020.12.10 |