Zlecenie wykonania dekoratora

96
def make_bold(fn):
    return lambda : "<b>" + fn() + "</b>"

def make_italic(fn):
    return lambda : "<i>" + fn() + "</i>"

@make_bold
@make_italic
def hello():
  return "hello world"

helloHTML = hello()

Wynik: "<b><i>hello world</i></b>"

Z grubsza rozumiem o dekoratorach i jak to działa z jednym z nich w większości przykładów.

W tym przykładzie jest ich 2. Z danych wyjściowych wynika, że @make_italicnajpierw jest wykonywany @make_bold.

Czy to oznacza, że ​​w przypadku funkcji dekoracyjnych najpierw uruchomi funkcję, a następnie przesunie się do góry dla innych dekoratorów? Tak jak @make_italicwtedy @make_bold, zamiast odwrotnie.

Czy to oznacza, że ​​różni się od standardowego podejścia odgórnego w większości języków programowania? Tylko w tym przypadku dekoratora? A może się mylę?

Nowicjusz
źródło
4
tak, zaczyna się od dołu do góry, przekazując wynik do następnego
Padraic Cunningham
1
Komentarz @PadraicCunningham jest również ważną częścią odpowiedzi. Miałem podobny problem ( stackoverflow.com/questions/47042196/… )
shookees.
Powiedziałbym, że nadal jest odgórny, w tym sensie, że a(b(x))jest odgórny (jeśli wyobrażasz sobie, że jest podzielony na 3 wiersze)
joel

Odpowiedzi:

132

Dekoratorzy zawijają funkcję, którą dekorują. Tak make_boldozdobiony efektem make_italicdekoratora, który zdobił hellofunkcję.

@decoratorSkładnia jest naprawdę tylko cukier syntaktyczny; następujące:

@decorator
def decorated_function():
    # ...

jest naprawdę wykonywany jako:

def decorated_function():
    # ...
decorated_function = decorator(decorated_function)

zastąpienie oryginalnego decorated_functionprzedmiotu tym, co decorator()zwrócono.

Układanie dekoratorów w stosy powtarza ten proces na zewnątrz .

Więc twoja próbka:

@make_bold
@make_italic
def hello():
  return "hello world"

można rozszerzyć do:

def hello():
  return "hello world"
hello = make_bold(make_italic(hello))

Kiedy dzwonisz hello()teraz, tak naprawdę wywołujesz obiekt zwrócony przez make_bold(). make_bold()zwrócił a, lambdaktóry wywołuje funkcję make_boldopakowaną, która jest wartością zwracaną make_italic(), która jest również lambdą wywołującą oryginał hello(). Rozszerzanie wszystkich otrzymywanych połączeń:

hello() = lambda : "<b>" + fn() + "</b>" #  where fn() ->
    lambda : "<i>" + fn() + "</i>" # where fn() -> 
        return "hello world"

więc wynik wygląda następująco:

"<b>" + ("<i>" + ("hello world") + "</i>") + "</b>"
Martijn Pieters
źródło
Rozumiem. Ale czy to oznacza, że ​​gdy w tym przypadku są 2 opakowania, IDE automatycznie wykryje i zawinie wynik pierwszego opakowania? Ponieważ tak myślałem @make_bold #make_bold = make_bold(hello) @make_italic #make_italic = make_italic (hello)? Nie jestem pewien, czy opierając się na tym, zawinie pierwszy wynik. Czy w przypadku 2 opakowań IDE użyje make_bold(make_italic(hello))tego, o czym wspomniałeś, zamiast tego, co udostępniłem?
Nowicjusz
3
@Newbie: Twoje IDE nic tutaj nie robi; to Python wykonuje zawijanie. Pokazałem ci w mojej ostatniej próbce, która make_bold()zawija dane wyjściowe make_italic(), które zostały użyte do zawijania hello, a więc odpowiednik make_bold(make_italic(hello)).
Martijn Pieters
Czy możesz podać wersję tego kodu bez użycia lambda? Próbowałem .format, ale nie działa. A dlaczego w tym przykładzie użyto lambda? Próbuję zrozumieć lambdę i jak to działa w tym przykładzie, ale nadal mam problemy. Rozumiem, że lambda jest jak funkcje jednowierszowe, które można łatwo przekazać w porównaniu z normą funkcji def?
Nowicjusz
def inner: return "<b>" + fn() + "</b>", to return innerbyłaby „zwykła” wersja funkcji; nie jest to duża różnica.
Martijn Pieters
Zawsze mylę się co do porządku. „… dekoratory zostaną zastosowane zaczynając od tego, który jest najbliższy stwierdzeniu„ def ”„ Nazywam to „na lewą stronę”. Myślę, że Martijn nazywa to „zewnętrzną”. Oznacza to, że make_italic dekorator jest wykonywany przed make_bold dekoratorem , ponieważ make_italicjest najbliżej def. Jednak zapominam, że dekorowana kolejność wykonania kodu: make_bold dekorowana (tj. Pogrubiona lambda) jest wykonywana jako pierwsza, a następnie make_italic dekorowana lambda (czyli lambda kursywa).
The Red Pea