878 votes

Que fait functools.wraps ?

Dans un commentaire sur la réponse à une autre question, quelqu'un a dit qu’ils n’étaient pas sûrs de ce que functools.wraps a été fait. Donc je pose cette question afin qu’il y aura un enregistrement de celui-ci sur StackOverflow pour référence ultérieure : ce que functools.wraps ne fait, exactement ?

1423voto

Eli Courtwright Points 53071

Lorsque vous utilisez un décorateur, à remplacer une fonction par une autre. En d'autres termes, si vous avez un décorateur

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

puis, quand vous dites

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

c'est exactement la même chose que de dire

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

et votre fonction f est remplacé par la fonction with_logging. Malheureusement, cela signifie que si vous puis dire

print f.__name__

il permet d'imprimer with_logging car c'est le nom de votre nouvelle fonction. En fait, si vous regardez la docstring pour f, il sera vide car with_logging n'a pas de docstring, et donc la docstring vous avez écrit ne sera plus là. Aussi, si vous regardez la pydoc résultat pour cette fonction, il ne sera pas répertorié comme prenant un argument x; au lieu de cela, il va être répertorié comme prenant *args et **kwargs parce que c'est ce with_logging prend.

Si à l'aide d'un décorateur toujours signifie la perte de cette information sur une fonction, il serait un sérieux problème. C'est pourquoi nous avons functools.wraps. Cela prend une fonction utilisée dans un décorateur et ajoute les fonctionnalités de copier le nom de la fonction, docstring, les arguments de la liste, etc. Et depuis wraps est lui-même un décorateur, le code suivant fonctionne correctement:

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'

30voto

Josh Points 2820

J'ai très souvent l'utilisation de classes, plutôt que des fonctions, pour ma décorateurs. J'ai eu quelques problèmes avec cela à cause d'un objet n'aurez pas tous les mêmes attributs que l'on attend d'une fonction. Par exemple, un objet de ne pas avoir l'attribut __name__. J'ai eu un problème spécifique avec ce qui était assez difficile de retrouver la trace où Django sur les rapports de l'erreur "objet n'a pas d'attribut '__name__'". Malheureusement, pour la classe de style décorateurs, je ne crois pas que @wrap va faire le travail. J'ai plutôt créé une base de décorateur classe comme ceci:

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)

Cette classe sert de proxy pour tous les attribut d'appels à la fonction qui est décoré. Ainsi, vous pouvez maintenant créer un simple décorateur qui vérifie que les 2 arguments sont spécifiés comme suit:

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

        return self.func(*args)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X