Decorators with parameters in Python

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Devanther
User
Beiträge: 13
Registriert: Samstag 23. März 2019, 21:45

Hallo,

https://repl.it/@tecladocode/CP1605-Dec ... parameters

kann mir jemand erklären was dieser Code macht. Für mich unglaublich schwer zu verstehen.
Was passiert bei der Ausgabe?
Kann mir jemand erklären SCHRITT FÜR SCHRITT erklären was der Code macht?

Was macht das?
@functools.wraps(func)

Ich weiss gar nicht wo ich anfangen soll, diesen Code zu verstehen!
Kann mir jemand schrittweise durch den Code helfen, es sind paar Zeilen Code aber trotzdem sehr schwer.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Die Dekorator-Syntax ist syntaktischer Zucker fuer das hier:

Code: Alles auswählen


def meindekorator(func):
    return func # tut nix

@meindekorator
def eine_funktion():
      pass

# ist exakt gleich zu dem hier:

def eine_funktion():
      pass

eine_funktion = meindekorator(eine_funktion)
Ein Dekorator ist also nichts anderes als

- eine Funktion
- die ein Argument bekommt
- und deren Rueckgabe dann an Stelle des urspruenglichen Funktion an den Namen gebunden wird.

Und wenn man einem solchen Dekorator Parameter verpassen will, dann muss man

- eine Funktion mit Argumenten erstellen
- die als Rueckgabewert eine Funktion liefert, die dem Dekorator Protokoll folgt

Code: Alles auswählen

def mein_parametrisierter_dekorator(name):
       def dekorator(func):
             def namensausgabe(*a, **kw):
                    print("hallo", name)
                    return func(*a, **kw) # einfach den eigentlichen Funktionsaufruf machen
              return namensausgabe # das wird die neue Funktion
       return dekorator # hier geben wir den eigentlichen Dekorator zurueck
       
       
@mein_parametrisierter_dekorator("karl heinz")
def funktion(a, b, c):
       print(a, b, c)
       
funktion(1, 2, "tausend") 
Benutzeravatar
__blackjack__
User
Beiträge: 14032
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Erst einmal der Code um den es hier geht, damit man die Frage auch versteht wenn der mal nicht mehr hinter dem externen Link vorhanden ist:

Code: Alles auswählen

import functools

user = {'username': 'jose123', 'access_level': 'user'}


def user_has_permission(access_level):
    def my_decorator(func):
        @functools.wraps(func)
        def secure_func(panel):
            if user.get('access_level') == access_level:
                return func(panel)
        return secure_func
    return my_decorator


@user_has_permission('user')
def my_function(panel):
    """
    Allows us to retrieve the password for the admin panel.
    """
    return f'Password for {panel} panel is 1234.'


print(my_function.__name__)
print(my_function('movies'))
@Devanther: Das `functools.wraps()` kannst Du aus der Betrachtung auch erst einmal heraus lassen, weil das erst einmal keinen direkten Einfluss auf die Funktion des Code hat, sondern ”nur” wenn man „introspection“ der dekorierten Funktion betreibt. Also letztlich betrifft es den Code dann doch, aber der Unterschied den man dann sehen kann, der ist dann hoffentlich zusammen mit der Dokumentation von `functools.wraps()` erhellend.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
nezzcarth
User
Beiträge: 1754
Registriert: Samstag 16. April 2011, 12:47

Dekoratoren müssen Callables sein. Daher kann man Dekoratoren neben der Variante mit Funktionen/Closures auch über Klassen definieren, die die __call__ Methode implementieren. Aus dem Beispiel wird dann:

Code: Alles auswählen

# Die Python Konvention, für die Schreibweise von Klassen und Funktionennamen wird hier etwas schwierig
class UserHasPermission:
    def __init__(self, access_level):
        self.access_level = access_level
    
    def __call__(self, func):
        def inner(panel):
            if user.get('access_level') == self.access_level:
                return func(panel)
        return inner 
Dadurch spart man (optisch) eine Verschachtelungsebene (bzw. verschiebt sie sich in __init__). Bei Dekoratoren mit Argumenten finde ich diese Variante leichter zu lesen und zu verstehen. Wichtig ist bei beiden Varianten, dass man sich versucht zu verbildlichen, wie die Aufrufreihefolge ist, um zu verstehen, welcher Parameter in welcher Signatur stehen muss. Wenn das Konzept von Dekoratoren für dich insgesamt neu ist, ist es vielleicht einfacher, zunächst mit Dekoratoren ohne Argumente zu beginnen. Da braucht man eine "Ebene" weniger.
Benutzeravatar
__blackjack__
User
Beiträge: 14032
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@nezzcarth: Da musst Du dann noch das `functools.wraps()` unterbringen, wenn die Signatur und Dokumentation erhalten bleiben sollen.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
nezzcarth
User
Beiträge: 1754
Registriert: Samstag 16. April 2011, 12:47

@__blackjack__:

Code: Alles auswählen

...
    def __call__(self, func):
        @functools.wraps(func)
        def inner(panel):
            if user.get('access_level') == self.access_level:
                return func(panel)
        return inner 
        
@UserHasPermission('user')
def my_function2(panel):
...

In [5]: ?my_function2                                                                                                                                                                         
Signature: my_function2(panel)
Docstring: Allows us to retrieve the password for the admin panel.
File:      ~/<ipython-input-3-452b85bb219b>
Type:      function
Besser? ;)
Benutzeravatar
__blackjack__
User
Beiträge: 14032
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Jup. Wobei ich gestehen muss, das ich das in der Regel auch nur für Programme mache wo ich auch eine Dokumentation zu erstelle, weil da wird das dann wirklich wichtig wenn man Teile dafür aus dem Quelltext/Docstrings herausziehen lässt.

Wobei nur besser, aber noch nicht perfekt. Um näher an perfekt zu kommen gibt es das externe `decorator`-Modul. 🙂
Zuletzt geändert von __blackjack__ am Mittwoch 15. April 2020, 16:51, insgesamt 1-mal geändert.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Es gibt auch manchmal 3rd-party-Libs, die das wollen, weil die eine Signatur inspizieren, und dann auf die Fresse fallen, wenn die generisch ist.
Antworten