web-dev-qa-db-fra.com

Python décorateur comme méthode statique

J'essaie d'écrire une classe python qui utilise une fonction de décorateur qui a besoin d'informations sur l'état de l'instance. Cela fonctionne comme prévu, mais si je fais explicitement du décorateur une méthode statique, j'obtiens le erreur suivante:

Traceback (most recent call last):
  File "tford.py", line 1, in <module>
    class TFord(object):
  File "tford.py", line 14, in TFord
    @ensure_black
TypeError: 'staticmethod' object is not callable

Pourquoi?

Voici le code:

class TFord(object):
    def __init__(self, color):
        self.color = color

    @staticmethod
    def ensure_black(func):
        def _aux(self, *args, **kwargs):
            if self.color == 'black':
                return func(*args, **kwargs)
            else:
                return None
        return _aux

    @ensure_black
    def get():
        return 'Here is your shiny new T-Ford'

if __name__ == '__main__':
    ford_red = TFord('red')
    ford_black = TFord('black')

    print ford_red.get()
    print ford_black.get()

Et si je supprime simplement la ligne @staticmethod, tout fonctionne, mais je ne comprends pas pourquoi. Ne devrait-il pas avoir besoin de self comme premier argument?

43
wkz

Ce n'est pas ainsi que staticmethod est censé être utilisé. staticmethod les objets sont descripteurs qui renvoient l'objet encapsulé, donc ils ne fonctionnent que lorsqu'ils sont accédés en tant que classname.staticmethodname. Exemple

class A(object):
    @staticmethod
    def f():
        pass
print A.f
print A.__dict__["f"]

impressions

<function f at 0x8af45dc>
<staticmethod object at 0x8aa6a94>

Dans la portée de A, vous obtiendrez toujours ce dernier objet, qui n'est pas appelable.

Je recommanderais fortement de déplacer le décorateur vers la portée du module - il ne semble pas appartenir à la classe. Si vous voulez le garder à l'intérieur de la classe, n'en faites pas un staticmethod, mais plutôt simplement del à la fin du corps de la classe - il n'est pas destiné à être utilisé de l'extérieur la classe dans ce cas.

43
Sven Marnach

Les classes Python sont créées au moment de l'exécution, après avoir évalué le contenu de la déclaration de classe. La classe est évaluée en affectant toutes les variables et fonctions déclarées à un dictionnaire spécial et en utilisant ce dictionnaire pour appeler type.__new__ (voir personnalisation de la création de classe ).

Alors,

class A(B):
    c = 1

est équivalent à:

A = type.__new__("A", (B,), {"c": 1})

Lorsque vous annotez une méthode avec @staticmethod, une magie spéciale se produit APRÈS la création de la classe avec type.__new__. Dans la portée de la déclaration de classe, la fonction @staticmethod n'est qu'une instance d'un objet staticmethod, que vous ne pouvez pas appeler. Le décorateur devrait probablement être déclaré au-dessus de la définition de classe dans le même module OR dans un module "décorer" séparé (dépend du nombre de décorateurs dont vous disposez). En général, les décorateurs doivent être déclarés en dehors de une classe. Une exception notable est la classe de propriété (voir propriétés ). Dans votre cas, avoir le décorateur à l'intérieur d'une déclaration de classe pourrait avoir un sens si vous aviez quelque chose comme une classe de couleur:

class Color(object):

    def ___init__(self, color):
        self.color = color

     def ensure_same_color(f):
         ...

black = Color("black")

class TFord(object):
    def __init__(self, color):
        self.color = color

    @black.ensure_same_color
    def get():
        return 'Here is your shiny new T-Ford'
8
jfocht

Aujourd'hui, je voulais que le décorateur de méthode statique soit utilisé uniquement dans ma classe.

Le problème est que la méthode statique qui essaie d'être utilisée comme décorateur est en fait un objet de méthode statique et n'est pas appelable.

Solution : l'objet de méthode statique a la méthode __get__ qui prend n'importe quel argument et retourne la méthode réelle: documentation python Python 3.5 et plus:

class StaticMethod(object):
"Emulate PyStaticMethod_Type() in Objects/funcobject.c"

def __init__(self, f):
    self.f = f

def __get__(self, obj, objtype=None):
    return self.f

La solution min avec laquelle je suis venu est:

class A():
def __init__(self):
    self.n =  2

@staticmethod
def _addtoglobaltime(func):
    from functools import wraps
    @wraps(func)
    def wrapper(*args, **kwargs):
        self = args[0]
        response = func(*args, **kwargs)
        return self.n, response

    return wrapper

@_addtoglobaltime.__get__('this can be anything')
def get(self):
    return self.n**2

if __name__ == '__main__':
    a = A()
    print(a.get())

Imprime (2, 4)

0
usernumber124153