Jak usunąć dekoratory z funkcji w Pythonie
Powiedzmy, że mam:
def with_connection(f):
def decorated(*args, **kwargs):
f(get_connection(...), *args, **kwargs)
return decorated
@with_connection
def spam(connection):
# Do something
Chcę przetestować funkcję spam
bez konieczności konfigurowania połączenia (lub cokolwiek robi dekorator).
Biorąc pod uwagę spam
, Jak usunąć dekorator z niego i uzyskać podstawową funkcję "niedekorowaną"?
10 answers
W ogólnym przypadku, nie możesz, ponieważ
@with_connection
def spam(connection):
# Do something
Jest równoważne
def spam(connection):
# Do something
spam = with_connection(spam)
Co oznacza, że "oryginalny" spam może już nie istnieć. A (nie za ładny) hack byłby taki:
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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-22 15:42:28
Rozwiązanie Balpha może być bardziej uogólnione za pomocą tego meta-dekoratora:
def include_original(dec):
def meta_decorator(f):
decorated = dec(f)
decorated._original = f
return decorated
return meta_decorator
Następnie możesz udekorować swoich dekoratorów za pomocą @include_original, a każdy z nich będzie miał testowalną (niedekorowaną) wersję schowaną w środku.
@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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-21 10:15:43
Pojawił się mały update dla tego pytania. Jeśli używasz Pythona 3, możesz użyć właściwości __wrapped__
dla dekoratorów ze stdlib.
Oto przykład z Python Cookbook, wydanie 3, sekcja 9.3 rozpakowywanie dekoratorów
>>> @somedecorator
>>> def add(x, y):
... return x + y
...
>>> orig_add = add.__wrapped__
>>> orig_add(3, 4)
7
>>>
Jeśli próbujesz rozpakować funkcję z niestandardowego dekoratora, funkcja dekoratora musi użyć wraps
funkcji z functools
Zobacz dyskusję w Python Cookbook, wydanie 3, sekcja 9.2 zachowując metadane funkcji podczas pisania dekoratorzy
>>> from functools import wraps
>>> def somedecoarator(func):
... @wraps(func)
... def wrapper(*args, **kwargs):
... # decorator implementation here
... # ...
... return func(*args, kwargs)
...
... return wrapper
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-09 21:30:21
Oto Fuglyhackthatwill Work Foryourexamplebuticantpromiseanythingelse:
orig_spam = spam.func_closure[0].cell_contents
Edit: W przypadku funkcji / metod dekorowanych więcej niż raz i bardziej skomplikowanych dekoratorów możesz spróbować użyć poniższego kodu. Polega ona na tym, że funkcje dekorowane są __name__d inaczej niż funkcja oryginalna.
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>
Nie jest to jednak głupi dowód. Nie powiedzie się, jeśli nazwa funkcji zwróconej przez dekoratora jest taka sama jak dekorowana. Order of sprawdzanie hasattr () jest również heurystyką, istnieją łańcuchy dekoracji, które zwracają błędne wyniki w każdym przypadku.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-22 16:59:05
Zamiast robić...
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)
Możesz to zrobić...
def with_connection(f):
...
def spam_f(connection):
...
spam = with_connection(spam_f)
...czyli wszystko, co robi składnia @decorator
- wtedy możesz oczywiście uzyskać dostęp do oryginału spam_f
normalnie.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-20 14:36:57
Możesz teraz użyć pakietu undekorated :
>>> from undecorated import undecorated
>>> undecorated(spam)
Przechodzi przez kłopotliwe przekopywanie się przez wszystkie warstwy różnych dekoratorów, dopóki nie osiągnie dolnej funkcji i nie wymaga zmiany oryginalnych dekoratorów. Działa zarówno na Pythonie 2, jak i Pythonie 3.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-20 14:40:03
Zwykłe podejście do testowania takich funkcji polega na tym, aby dowolne zależności, takie jak get_connection, były konfigurowalne. Następnie możesz obejść go za pomocą makiety podczas testowania. Zasadniczo to samo co dependency injection w świecie Javy, ale o wiele prostsze dzięki dynamicznej naturze Pythons.
Kod może wyglądać mniej więcej tak:
# 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"
W zależności od kodu można znaleźć lepszy obiekt niż dekorator do przyklejenia funkcji fabrycznej. Problem z posiadaniem go na dekorator jest to, że trzeba pamiętać, aby przywrócić go do starej wartości w metodzie teardown.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-22 21:43:04
Dodaj dekoratora do niczego:
def do_nothing(f):
return f
Po zdefiniowaniu lub zaimportowaniu with_connection, ale zanim przejdziesz do metod, które używają go jako dekoratora, dodaj:
if TESTING:
with_connection = do_nothing
Wtedy, jeśli ustawisz global TESTING na True, zastąpisz with_connection dekoratorem do-nothing.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-19 08:23:38
Dobrą praktyką jest dekorowanie dekoratorów z functools.wraps
tak:
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
Począwszy od Pythona 3.2, to automatycznie doda atrybut __wrapped__
, który pozwala pobrać oryginalną, niedekorowaną funkcję:
>>> spam.__wrapped__
<function spam at 0x7fe4e6dfc048>
Jednak zamiast ręcznego dostępu do atrybutu __wrapped__
, lepiej użyć inspect.unwrap
:
>>> inspect.unwrap(spam)
<function spam at 0x7fe4e6dfc048>
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-20 14:40:50
Oryginalna funkcja jest przechowywana w spam.__closure__[0].cell_contents
.
Dekorator wykorzystuje zamknięcie, aby powiązać oryginalną funkcję z dodatkową warstwą funkcjonalności. Pierwotna funkcja musi być przechowywana w komórce zamykającej utrzymywanej przez jedną z funkcji w zagnieżdżonej strukturze dekoratora.
Przykład:
>>> 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
Jest to podstawowa zasada undekorated , możesz odwołać się do kodu źródłowego po więcej szczegółów.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-24 11:39:12