Frage

Angenommen, ich habe einen Dekorateur geschrieben, dass etwas sehr allgemein der Fall ist. Zum Beispiel könnte es alle Argumente auf einen bestimmten Typ konvertieren, führen Sie die Protokollierung, implementieren memoization, etc.

Hier ist ein Beispiel:

def args_as_ints(f):
    def g(*args, **kwargs):
        args = [int(x) for x in args]
        kwargs = dict((k, int(v)) for k, v in kwargs.items())
        return f(*args, **kwargs)
    return g

@args_as_ints
def funny_function(x, y, z=3):
    """Computes x*y + 2*z"""
    return x*y + 2*z

>>> funny_function("3", 4.0, z="5")
22

Alles gut so weit. Es gibt allerdings ein Problem. Die dekorierte Funktion behält nicht die Dokumentation der ursprünglichen Funktion:

>>> help(funny_function)
Help on function g in module __main__:

g(*args, **kwargs)

Glücklicherweise gibt es eine Abhilfe:

def args_as_ints(f):
    def g(*args, **kwargs):
        args = [int(x) for x in args]
        kwargs = dict((k, int(v)) for k, v in kwargs.items())
        return f(*args, **kwargs)
    g.__name__ = f.__name__
    g.__doc__ = f.__doc__
    return g

@args_as_ints
def funny_function(x, y, z=3):
    """Computes x*y + 2*z"""
    return x*y + 2*z

Dieses Mal der Name der Funktion und Dokumentation korrekt sind:

>>> help(funny_function)
Help on function funny_function in module __main__:

funny_function(*args, **kwargs)
    Computes x*y + 2*z

Aber es gibt noch ein Problem: die Funktionssignatur ist falsch. Die Information "* args, ** kwargs" ist so gut wie nutzlos.

Was ist zu tun? Ich kann mich zwei einfache, aber fehlerhaft Abhilfen:

1 - Fügen Sie die korrekte Signatur im docstring:

def funny_function(x, y, z=3):
    """funny_function(x, y, z=3) -- computes x*y + 2*z"""
    return x*y + 2*z

Das ist schlecht, weil die Vervielfältigung. Die Signatur wird immer noch nicht richtig in automatisch generierte Dokumentation gezeigt werden. Es ist einfach, um die Funktion zu aktualisieren, und vergessen Sie das docstring ändern oder einen Tippfehler zu machen. [Und ja, ich bin mir dessen bewusst, dass die docstring bereits die Funktion Körper dupliziert. Bitte ignorieren Sie diese; funny_function ist nur ein willkürliches Beispiel. ]

2 - nicht einen Dekorateur verwenden oder einen Spezial-Dekorateur für jede spezifische Signatur verwenden:

def funny_functions_decorator(f):
    def g(x, y, z=3):
        return f(int(x), int(y), z=int(z))
    g.__name__ = f.__name__
    g.__doc__ = f.__doc__
    return g

Dies funktioniert für eine Reihe von Funktionen in Ordnung, die identische Signatur haben, aber es ist nutzlos im Allgemeinen. Wie ich am Anfang gesagt, ich will generell ganz in der Lage sein Dekorateure zu verwenden.

Ich bin auf der Suche nach einer Lösung, die vollständig allgemein und automatisch.

Die Frage ist also: Gibt es eine Möglichkeit, die eingerichtete Funktion Signatur zu bearbeiten, nachdem es erstellt wurde

Ansonsten kann ich schreiben einen Dekorateur, der die Funktionssignatur extrahiert und verwendet diese Informationen anstelle von „* kwargs, ** kwargs“, wenn die eingerichtete Funktion konstruieren? Wie extrahiere ich diese Informationen? Wie soll ich die eingerichtete Funktion konstruieren - mit exec

Jede andere Ansätze?

War es hilfreich?

Lösung

  1. Installieren Dekorateur Modul:

    $ pip install decorator
    
  2. anpassen Definition von args_as_ints():

    import decorator
    
    @decorator.decorator
    def args_as_ints(f, *args, **kwargs):
        args = [int(x) for x in args]
        kwargs = dict((k, int(v)) for k, v in kwargs.items())
        return f(*args, **kwargs)
    
    @args_as_ints
    def funny_function(x, y, z=3):
        """Computes x*y + 2*z"""
        return x*y + 2*z
    
    print funny_function("3", 4.0, z="5")
    # 22
    help(funny_function)
    # Help on function funny_function in module __main__:
    # 
    # funny_function(x, y, z=3)
    #     Computes x*y + 2*z
    

Python 3.4 +

functools.wraps() von stdlib bewahrt Signaturen seit Python 3.4:

import functools


def args_as_ints(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        args = [int(x) for x in args]
        kwargs = dict((k, int(v)) for k, v in kwargs.items())
        return func(*args, **kwargs)
    return wrapper


@args_as_ints
def funny_function(x, y, z=3):
    """Computes x*y + 2*z"""
    return x*y + 2*z


print(funny_function("3", 4.0, z="5"))
# 22
help(funny_function)
# Help on function funny_function in module __main__:
#
# funny_function(x, y, z=3)
#     Computes x*y + 2*z

functools.wraps() ist verfügbar mindestens seit Python 2.5 aber es funktioniert nicht erhalten die Unterschrift es:

help(funny_function)
# Help on function funny_function in module __main__:
#
# funny_function(*args, **kwargs)
#    Computes x*y + 2*z

Hinweis:. *args, **kwargs statt x, y, z=3

Andere Tipps

Dies ist mit Python-Standardbibliothek functools gelöst und speziell functools.wraps Funktion, die ist so konzipiert, „ eine Wrapper-Funktion aktualisiert werden wie die eingewickelt Funktion aussehen “. Es ist das Verhalten auf Python-Version hängt jedoch wie unten gezeigt. Angewandt auf das Beispiel von der Frage, würde der Code wie folgt aussehen:

from functools import wraps

def args_as_ints(f):
    @wraps(f) 
    def g(*args, **kwargs):
        args = [int(x) for x in args]
        kwargs = dict((k, int(v)) for k, v in kwargs.items())
        return f(*args, **kwargs)
    return g


@args_as_ints
def funny_function(x, y, z=3):
    """Computes x*y + 2*z"""
    return x*y + 2*z

Wenn 3 in Python ausgeführt, würde dies produzieren die folgende:

>>> funny_function("3", 4.0, z="5")
22
>>> help(funny_function)
Help on function funny_function in module __main__:

funny_function(x, y, z=3)
    Computes x*y + 2*z

Der einzige Nachteil ist, dass jedoch in Python 2, ist es nicht Argumentliste nicht aktualisiert die Funktion. Wenn in Python 2 ausgeführt wird, wird es produzieren:

>>> help(funny_function)
Help on function funny_function in module __main__:

funny_function(*args, **kwargs)
    Computes x*y + 2*z

Es gibt ein Dekorateur Modul mit decorator Dekorateur können Sie verwenden :

@decorator
def args_as_ints(f, *args, **kwargs):
    args = [int(x) for x in args]
    kwargs = dict((k, int(v)) for k, v in kwargs.items())
    return f(*args, **kwargs)

Dann wird die Signatur und die Hilfe des Verfahrens erhalten bleibt:

>>> help(funny_function)
Help on function funny_function in module __main__:

funny_function(x, y, z=3)
    Computes x*y + 2*z

EDIT: J. F. Sebastian wies darauf hin, dass ich nicht args_as_ints Funktion ändern hat - es wurde behoben

.

Werfen Sie einen Blick auf die Dekorateur Modul - speziell die < a href = "http://www.phyast.pitt.edu/~micheles/python/documentation.html#decorator-is-a-decorator" rel = "noreferrer"> Dekorateur Dekorateur, die dieses Problem löst .

Zweite Option:

  1. Installieren wrapt Modul:

$ easy_install wrapt

wrapt haben einen Bonus, Klasse Signatur erhalten.


import wrapt
import inspect

@wrapt.decorator def args_as_ints(wrapped, instance, args, kwargs): if instance is None: if inspect.isclass(wrapped): # Decorator was applied to a class. return wrapped(*args, **kwargs) else: # Decorator was applied to a function or staticmethod. return wrapped(*args, **kwargs) else: if inspect.isclass(instance): # Decorator was applied to a classmethod. return wrapped(*args, **kwargs) else: # Decorator was applied to an instancemethod. return wrapped(*args, **kwargs) @args_as_ints def funny_function(x, y, z=3): """Computes x*y + 2*z""" return x * y + 2 * z >>> funny_function(3, 4, z=5)) # 22 >>> help(funny_function) Help on function funny_function in module __main__: funny_function(x, y, z=3) Computes x*y + 2*z

Wie kommentierte oben in jfs Antwort ; wenn Sie mit Unterschrift in Bezug auf Aussehen betroffen sind (help und inspect.signature), dann functools.wraps verwendet, ist völlig in Ordnung.

Wenn Sie mit Unterschrift in Bezug auf Verhalten betroffen sind (insbesondere TypeError bei Argumenten Mismatch), hat functools.wraps es nicht bewahren. Sie sollten eher decorator für das, oder meine Verallgemeinerung seines Core-Engine verwenden, mit dem Namen makefun .

from makefun import wraps

def args_as_ints(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("wrapper executes")
        args = [int(x) for x in args]
        kwargs = dict((k, int(v)) for k, v in kwargs.items())
        return func(*args, **kwargs)
    return wrapper


@args_as_ints
def funny_function(x, y, z=3):
    """Computes x*y + 2*z"""
    return x*y + 2*z


print(funny_function("3", 4.0, z="5"))
# wrapper executes
# 22

help(funny_function)
# Help on function funny_function in module __main__:
#
# funny_function(x, y, z=3)
#     Computes x*y + 2*z

funny_function(0)  
# observe: no "wrapper executes" is printed! (with functools it would)
# TypeError: funny_function() takes at least 2 arguments (1 given)

Siehe auch diesen Beitrag über functools.wraps .

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