Frage

In einem Kommentar dazu Antwort auf eine andere Frage, sagte jemand, dass er nicht sicher sei, was functools.wraps Hat gemacht.Deshalb stelle ich diese Frage, damit es zur späteren Bezugnahme eine Aufzeichnung davon auf StackOverflow gibt:was macht functools.wraps genau tun?

War es hilfreich?

Lösung

Wenn Sie einen Dekorateur verwenden, Sie ersetzen eine Funktion mit einem anderen. Mit anderen Worten, wenn Sie einen Dekorateur

def logged(func):
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging

dann, wenn Sie sagen

@logged
def f(x):
   """does some math"""
   return x + x * x

es ist genau die gleiche wie zu sagen

def f(x):
    """does some math"""
    return x + x * x
f = logged(f)

und Ihre Funktion f ist mit der Funktion with_logging ersetzt. Leider bedeutet dies, dass, wenn man dann sagen,

print(f.__name__)

wird es drucken with_logging denn das ist der Name der neuen Funktion ist. In der Tat, wenn man sich das docstring für f aussieht, wird es leer sein, weil with_logging kein docstring hat, und so die docstring Sie nicht dort schrieben mehr sein. Auch, wenn Sie für diese Funktion am pydoc Ergebnis aussehen, wird es nicht als ein Argument x aufgeführt werden; stattdessen wird es als Einnahme *args und **kwargs aufgeführt werden, weil das ist, was with_logging nimmt.

Wenn ein Dekorateur immer gemeint mit diesen Informationen über eine Funktion zu verlieren, wäre es ein ernsthaftes Problem sein. Deshalb haben wir functools.wraps haben. Dies nimmt eine Funktion in einem Dekorateur verwendet und fügt die Funktionalität des Kopierens über die Funktionsnamen, docstring, Argumente Liste usw. Und da wraps ist selbst ein Dekorateur, der folgende Code funktioniert die richtige Sache:

from functools import wraps
def logged(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging

@logged
def f(x):
   """does some math"""
   return x + x * x

print(f.__name__)  # prints 'f'
print(f.__doc__)   # prints 'does some math'

Andere Tipps

Ich verwende sehr oft Klassen, anstatt Funktionen, für meine Dekorateure. Ich war ein paar Probleme mit diesem haben, weil ein Objekt nicht alle die gleichen Eigenschaften haben, die einer Funktion zu erwarten sind. Zum Beispiel hat ein Objekt nicht das Attribut __name__. Ich hatte ein spezifisches Problem mit diesem, das ziemlich schwer zu verfolgen, wo Django dem Fehler berichtet „Objekt hat kein Attribut‚__name__‘“. Leider für Klasse-Stil Dekorateure, ich glaube nicht, dass @wrap den Job zu tun. Ich habe erstellt stattdessen eine Basis Dekorateur-Klasse wie folgt:

class DecBase(object):
    func = None

    def __init__(self, func):
        self.__func = func

    def __getattribute__(self, name):
        if name == "func":
            return super(DecBase, self).__getattribute__(name)

        return self.func.__getattribute__(name)

    def __setattr__(self, name, value):
        if name == "func":
            return super(DecBase, self).__setattr__(name, value)

        return self.func.__setattr__(name, value)

Diese Klasse Proxies alle das Attribut ruft die Funktion über die eingerichtet wird. So können Sie jetzt einen einfachen Dekorateur erstellen, die überprüft, ob zwei Argumente angegeben werden, etwa so:

class process_login(DecBase):
    def __call__(self, *args):
        if len(args) != 2:
            raise Exception("You can only specify two arguments")

        return self.func(*args)

Ab Python 3.5+:

@functools.wraps(f)
def g():
    pass

Ist ein Alias ​​für g = functools.update_wrapper(g, f).Es bewirkt genau drei Dinge:

  • es kopiert die __module__, __name__, __qualname__, __doc__, Und __annotations__ Attribute von f An g.Diese Standardliste ist in WRAPPER_ASSIGNMENTS, Sie können es in der sehen functools-Quelle.
  • es aktualisiert die __dict__ von g mit allen Elementen aus f.__dict__.(sehen WRAPPER_UPDATES in der Quelle)
  • es setzt ein neues __wrapped__=f Attribut auf g

Die Konsequenz ist das g scheint den gleichen Namen, die gleiche Dokumentzeichenfolge, den gleichen Modulnamen und die gleiche Signatur zu haben wie f.Das einzige Problem ist, dass dies bezüglich der Signatur nicht wirklich stimmt:es ist nur das inspect.signature folgt standardmäßig Wrapper-Ketten.Sie können es überprüfen, indem Sie verwenden inspect.signature(g, follow_wrapped=False) wie im erklärt Dok.Das hat ärgerliche Folgen:

  • Der Wrapper-Code wird auch dann ausgeführt, wenn die angegebenen Argumente ungültig sind.
  • Der Wrapper-Code kann nicht einfach über seinen Namen aus den empfangenen *args, **kwargs auf ein Argument zugreifen.Tatsächlich müsste man alle Fälle (Positional, Schlüsselwort, Standard) behandeln und daher so etwas wie verwenden Signature.bind().

Jetzt gibt es ein wenig Verwirrung dazwischen functools.wraps und Dekoratoren, da ein sehr häufiger Anwendungsfall für die Entwicklung von Dekoratoren darin besteht, Funktionen zu verpacken.Aber beides sind völlig unabhängige Konzepte.Wenn Sie den Unterschied verstehen möchten, habe ich für beide Hilfsbibliotheken implementiert: Decopatch Dekorateure einfach zu schreiben, und Spaß machen einen signaturerhaltenden Ersatz bereitzustellen @wraps.Beachten Sie, dass makefun setzt auf den gleichen bewährten Trick wie der berühmte decorator Bibliothek.

Dies ist der Quellcode über Wraps:

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')

WRAPPER_UPDATES = ('__dict__',)

def update_wrapper(wrapper,
                   wrapped,
                   assigned = WRAPPER_ASSIGNMENTS,
                   updated = WRAPPER_UPDATES):

    """Update a wrapper function to look like the wrapped function

       wrapper is the function to be updated
       wrapped is the original function
       assigned is a tuple naming the attributes assigned directly
       from the wrapped function to the wrapper function (defaults to
       functools.WRAPPER_ASSIGNMENTS)
       updated is a tuple naming the attributes of the wrapper that
       are updated with the corresponding attribute from the wrapped
       function (defaults to functools.WRAPPER_UPDATES)
    """
    for attr in assigned:
        setattr(wrapper, attr, getattr(wrapped, attr))
    for attr in updated:
        getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
    # Return the wrapper so this can be used as a decorator via partial()
    return wrapper

def wraps(wrapped,
          assigned = WRAPPER_ASSIGNMENTS,
          updated = WRAPPER_UPDATES):
    """Decorator factory to apply update_wrapper() to a wrapper function

   Returns a decorator that invokes update_wrapper() with the decorated
   function as the wrapper argument and the arguments to wraps() as the
   remaining arguments. Default arguments are as for update_wrapper().
   This is a convenience function to simplify applying partial() to
   update_wrapper().
    """
    return partial(update_wrapper, wrapped=wrapped,
                   assigned=assigned, updated=updated)
  1. Voraussetzung: Sie müssen wissen, wie Dekorateure zu verwenden und speziell mit Wraps. Dieser Kommentar es ein wenig klar oder das link erklärt es auch ziemlich gut.

  2. Jedes Mal, wenn wir zum Beispiel verwenden: @wraps durch unsere eigene Wrapper-Funktion gefolgt. Gemäß den in dieser Link rel="nofollow, heißt es, dass

  

functools.wraps ist Komfortfunktion zum Aufrufen update_wrapper () als Funktion decorator, wenn eine Wrapper-Funktion definiert wird.

     

Es ist äquivalent zu einer teilweisen (update_wrapper, gewickelt = gewickelt, zugewiesen = zugewiesen, aktualisiert = aktualisiert).

So @wraps Dekorateur gibt tatsächlich einen Anruf functools.partial (func [* args] [** keywords]).

Die functools.partial () Definition besagt, dass

  

Die partielle () ist für eine teilweise Funktionsanwendung verwendet, die „einfriert“ ein Teil einer Funktion, die Argumente und / oder Schlüsselwörtern in ein neues Objekt mit einer vereinfachten Signatur führt. So kann beispielsweise teilweise () verwendet werden, um eine aufrufbare zu schaffen, die wie die int () Funktion verhält, wo das Basisargument Standardwert zu zwei:

>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18

Was mich zu dem Schluss bringt, dass @wraps einen Anruf zu Teil gibt () und es geht Ihre Wrapper-Funktion als Parameter zu. Die partielle () am Ende gibt die vereinfachte Version heißt das Objekt von dem, was in der Wrapper-Funktion ist und nicht die Wrapper-Funktion selbst.

Kurz gesagt, functools.wraps ist nur eine ganz normale Funktion. Betrachten wir diese offizielle Beispiel . Mit Hilfe der noreferrer"> Quellcode rel="nofollow

  1. Wraps (f) gibt ein Objekt, sagen O1 . Es ist eine Aufgabe der Klasse Partial
  2. Der nächste Schritt ist @ O1 ... , die die Dekorateur Notation in Python ist. Es bedeutet,
  

Wrapper = O1 .__ Aufruf __ (Wrapper)

Überprüfen der Implementierung von __ call __ , sehen wir nach diesem Schritt (die linke Seite) Wrapper das Objekt von self.func (* self.args, * args, ** newkeywords geführt wird ) Überprüfen der Erstellung von O1 in __ neue __ , wir wissen, self.func ist die Funktion update_wrapper . Es verwendet den Parameter * args , die rechte Seite Wrapper , wie der erste Parameter. Überprüfen Sie den letzten Schritt von update_wrapper , kann man die rechte Seite finden Sie unter Wrapper zurückgegeben wird, mit einigen Eigenschaften nach Bedarf modifiziert.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top