2013-03-25 13 views
5

Dlaczego ten dekorator nie działa z parametrem?Przekazywanie parametru do dekoratora w pythonie

def decAny(f0): 
    def wrapper(s0): 
     return "<%s> %s </%s>" % (any, f0(), any) 
    return wrapper 

@decAny('xxx') 
def test2(): 
    return 'test1XML' 

print(test2()) 

zawsze daje mi błąd mówiąc „str nie jest wymagalne” stara się wykonać ciąg zwrotny wewnątrz owijki() zamiast przetwarzać go i zwróci ciąg wynik

+1

Pomyśl o tym w ten sposób: Przed wami jeszcze _get_ do dekoracji 'test2', dzwonisz' decAny ('xxx') '. Ale 'decAny' przyjmuje funkcję,' f0', a nie ciąg znaków. Tak więc w pewnym momencie, 'f0()' spróbuje wywołać ''xxx''. – abarnert

+0

OK, ale jak w dekoratorze bez parametrów, dlaczego kompilator nie zakłada, że ​​pierwszym parametrem jest funkcja klienta ... – ZEE

+1

Nie jest to kwestia parametrów. Jeśli masz '@ decAny', to po prostu używając' decAny' jako dekoratora. Ale jeśli masz '@decAny()', to nazywa się 'decAny', zanim zdążysz się dekorować, tak samo jak' @decAny ('xxx') '. (To jest tak, jak przekazujesz funkcje jako wartości, przechowujesz je w zmiennych itp., A nie wywołując je.) – abarnert

Odpowiedz

13

dekoratorów funkcje te funkcje powrotu. Kiedy "przekazanie parametru do dekoratora" faktycznie wykonywane jest wywołanie funkcji, która zwraca dekorator. Tak więc decAny() powinna być funkcją, która zwraca funkcję, która zwraca funkcję.

To mogłoby wyglądać tak:

import functools 

def decAny(tag): 
    def dec(f0): 
     @functools.wraps(f0) 
     def wrapper(*args, **kwargs): 
      return "<%s> %s </%s>" % (tag, f0(*args, **kwargs), tag) 
     return wrapper 
    return dec 

@decAny('xxx') 
def test2(): 
    return 'test1XML' 

Przykład:

>>> print(test2()) 
<xxx> test1XML </xxx> 

Należy pamiętać, że oprócz ustalenia konkretnego problemu ty uderzanie ja również poprawić swój kod nieco dodając *args i **kwargs jako argumenty funkcji opakowanej i przekazywanie ich do wywołania f0 wewnątrz dekoratora. Dzięki temu można udekorować funkcję, która akceptuje dowolną liczbę argumentów pozycyjnych lub nazwanych i nadal będzie działała poprawnie.

Można przeczytać o functools.wraps() tutaj:
http://docs.python.org/2/library/functools.html#functools.wraps

+0

[PEP 318] (http://www.python.org/dev/peps/pep-0318/) ma przykłady pokazujące ten wzorzec (wymuszanie atrybutów lub interfejsów typu, 'synchronizacja' itp.). – abarnert

+1

Jeśli zamierzasz dodać '* args, ** kwargs', aby ulepszyć swój kod (bez wyjaśnienia dlaczego), prawdopodobnie również chcesz dodać' functools.wraps'. – abarnert

+0

@abarnert Dzięki za sugestię, dodano 'functools.wraps' i dodatkowe wyjaśnienie. –

1

Jest to dobry przykład z "Mark Lutz - Learning Python" book:

def timer(label=''): 
    def decorator(func): 
     def onCall(*args): # Multilevel state retention: 
      ...    # args passed to function 
      func(*args)  # func retained in enclosing scope 
      print(label, ... # label retained in enclosing scope 
     return onCall 
    return decorator   # Returns the actual decorator 

@timer('==>')    # Like listcomp = timer('==>')(listcomp) 
def listcomp(N): ...   # listcomp is rebound to new onCall 

listcomp(...)    # Really calls onCall