63 votes

Python décorateur fait la fonction oubliez pas qu'il appartient à une classe

Je suis en train d'écrire un décorateur pour faire de la journalisation:

def logger(myFunc):
    def new(*args, **keyargs):
        print 'Entering %s.%s' % (myFunc.im_class.__name__, myFunc.__name__)
        return myFunc(*args, **keyargs)

    return new

class C(object):
    @logger
    def f():
        pass

C().f()

Je voudrais que cette impression:

Entering C.f

mais au lieu de cela, je reçois ce message d'erreur:

AttributeError: 'function' object has no attribute 'im_class'

Sans doute c'est quelque chose à voir avec la notion de "mafonction" dedans "logger", mais je n'ai aucune idée de ce qu'.

51voto

Eli Courtwright Points 53071

Claudiu la réponse est correcte, mais vous pouvez aussi tricher en obtenant le nom de la classe hors de l' self argument. Cela donnera trompeuse journal des états en cas de succession, mais vais vous dire la classe de l'objet dont la méthode est appelée. Par exemple:

from functools import wraps  # use this to preserve function signatures and docstrings
def logger(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print "Entering %s.%s" % (args[0].__class__.__name__, func.__name__)
        return func(*args, **kwargs)
    return with_logging

class C(object):
    @logger
    def f(self):
        pass

C().f()

Comme je l'ai dit, cela ne fonctionne pas correctement dans les cas où vous avez hérité d'une fonction d'une classe parent; dans ce cas, vous pourriez dire

class B(C):
    pass

b = B()
b.f()

et le message Entering B.f où vous voulez avoir le message d' Entering C.f puisque c'est de la bonne classe. D'autre part, cela peut être acceptable, dans ce cas, je vous recommande cette approche sur Claudiu de la suggestion.

29voto

ianb Points 659

Les fonctions de devenir méthodes lors de l'exécution. C'est, quand vous arrivez C.f , vous obtenez un lié à la fonction (et C.f.im_class is C). Au moment où la fonction est définie, il est juste une simple fonction, il n'est lié à aucune classe. Cette délié et dissociée de la fonction est ce qui est décoré par l'enregistreur.

self.__class__.__name__ va vous donner le nom de la classe, mais vous pouvez également utiliser des descripteurs pour ce faire dans un peu plus de manière générale. Ce modèle est décrit dans un billet de blog sur les Décorateurs et les Descripteurs, et une mise en œuvre de votre enregistreur de décorateur en particulier ressemblerait à:

class logger(object):
    def __init__(self, func):
        self.func = func
    def __get__(self, obj, type=None):
        return self.__class__(self.func.__get__(obj, type))
    def __call__(self, *args, **kw):
        print 'Entering %s' % self.func
        return self.func(*args, **kw)

class C(object):
    @logger
    def f(self, x, y):
        return x+y

C().f(1, 2)
# => Entering <bound method C.f of <__main__.C object at 0x...>>

Évidemment, la sortie peut être amélioré (en utilisant, par exemple, getattr(self.func, 'im_class', None)), mais cette tendance générale va travailler pour les deux méthodes et les fonctions. Cependant, il va pas travailler pour des classes de style (mais il suffit de ne pas utiliser ceux-ci ;)

18voto

Denis Ryzhkov Points 331

Les idées proposées ici sont excellents, mais ils ont quelques inconvénients:

  1. inspect.getouterframes et args[0].__class__.__name__ ne sont pas adaptés pour les fonctions ordinaires et la statique des méthodes.
  2. __get__ doit être dans une classe, qui est rejeté par @wraps.
  3. @wraps lui-même devrait être de cacher les traces de mieux.

Donc, j'ai combiné des idées à partir de cette page, des liens, docs et de mon propre chef,
et enfin trouvé une solution, qui manque de tous les trois inconvénients ci-dessus.

En conséquence, method_decorator:

  • Connaît la classe décoré de la méthode est lié.
  • Cache décorateur traces en répondant à des attributs système plus correctement que l' functools.wraps() n'.
  • Est couvert par l'unité-tests pour lié une instance indépendante-méthodes de la classe des méthodes statiques-les méthodes et la plaine de fonctions.

Utilisation:

pip install method_decorator
from method_decorator import method_decorator

class my_decorator(method_decorator):
    # ...

Voir la pleine unité-tests pour les détails d'utilisation.

Et voici que le code de l' method_decorator classe:

class method_decorator(object):

    def __init__(self, func, obj=None, cls=None, method_type='function'):
        # These defaults are OK for plain functions
        # and will be changed by __get__() for methods once a method is dot-referenced.
        self.func, self.obj, self.cls, self.method_type = func, obj, cls, method_type

    def __get__(self, obj=None, cls=None):
        # It is executed when decorated func is referenced as a method: cls.func or obj.func.

        if self.obj == obj and self.cls == cls:
            return self # Use the same instance that is already processed by previous call to this __get__().

        method_type = (
            'staticmethod' if isinstance(self.func, staticmethod) else
            'classmethod' if isinstance(self.func, classmethod) else
            'instancemethod'
            # No branch for plain function - correct method_type for it is already set in __init__() defaults.
        )

        return object.__getattribute__(self, '__class__')( # Use specialized method_decorator (or descendant) instance, don't change current instance attributes - it leads to conflicts.
            self.func.__get__(obj, cls), obj, cls, method_type) # Use bound or unbound method with this underlying func.

    def __call__(self, *args, **kwargs):
        return self.func(*args, **kwargs)

    def __getattribute__(self, attr_name): # Hiding traces of decoration.
        if attr_name in ('__init__', '__get__', '__call__', '__getattribute__', 'func', 'obj', 'cls', 'method_type'): # Our known names. '__class__' is not included because is used only with explicit object.__getattribute__().
            return object.__getattribute__(self, attr_name) # Stopping recursion.
        # All other attr_names, including auto-defined by system in self, are searched in decorated self.func, e.g.: __module__, __class__, __name__, __doc__, im_*, func_*, etc.
        return getattr(self.func, attr_name) # Raises correct AttributeError if name is not found in decorated self.func.

    def __repr__(self): # Special case: __repr__ ignores __getattribute__.
        return self.func.__repr__()

7voto

Claudiu Points 58398

Il semble que, bien que la classe est en cours de création, Python crée régulièrement des objets de fonction. Ils ne se unbound méthode objets par la suite. Sachant que c'est la seule façon que j'ai pu trouver pour faire ce que vous voulez:

def logger(myFunc):
    def new(*args, **keyargs):
        print 'Entering %s.%s' % (myFunc.im_class.__name__, myFunc.__name__)
        return myFunc(*args, **keyargs)

    return new

class C(object):
    def f(self):
        pass
C.f = logger(C.f)
C().f()

Cette fonction génère le résultat souhaité.

Si vous voulez enchaîner toutes les méthodes dans une classe, alors vous voudrez probablement de créer un wrapClass fonction, que vous pouvez ensuite utiliser comme ceci:

C = wrapClass(C)

6voto

Asa Ayers Points 1873

Fonctions de classe doit toujours prendre l'auto comme premier argument, de sorte que vous pouvez l'utiliser à la place de im_class.

def logger(myFunc):
    def new(self, *args, **keyargs):
        print 'Entering %s.%s' % (self.__class__.__name__, myFunc.__name__)
        return myFunc(self, *args, **keyargs)

    return new 

class C(object):
    @logger
    def f(self):
        pass
C().f()

j'ai d'abord voulu utiliser self.__name__ mais cela ne fonctionne pas parce que l'instance n'a pas de nom. vous devez utiliser self.__class__.__name__ pour obtenir le nom de la classe.

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