Quand dois-je utiliser @classmethod et quand def method (self)?

88

Lors de l'intégration d'une application Django que je n'avais jamais utilisée auparavant, j'ai trouvé deux façons différentes de définir des fonctions dans les classes. L'auteur semble les utiliser tous les deux très intentionnellement. Le premier est celui que j'utilise beaucoup moi-même:

class Dummy(object):

    def some_function(self,*args,**kwargs):
        do something here
        self is the class instance

L'autre est celui que je n'utilise pas, principalement parce que je ne comprends pas quand l'utiliser, et dans quel but:

class Dummy(object):

    @classmethod
    def some_function(cls,*args,**kwargs):
        do something here
        cls refers to what?

Dans la documentation Python, le classmethoddécorateur est expliqué avec cette phrase:

Une méthode de classe reçoit la classe comme premier argument implicite, tout comme une méthode d'instance reçoit l'instance.

Donc je suppose qu'il se clsréfère à Dummylui-même (le class, pas l'instance). Je ne comprends pas exactement pourquoi cela existe, car je pourrais toujours le faire:

type(self).do_something_with_the_class

Est-ce juste pour des raisons de clarté, ou ai-je manqué la partie la plus importante: des choses fantasmagoriques et fascinantes qui ne pourraient être faites sans cela?

marue
la source

Réponses:

70

Votre hypothèse est correcte - vous comprenez comment classmethod fonctionne.

Le pourquoi est que ces méthodes peuvent être appelées à la fois sur une instance OU sur la classe (dans les deux cas, l'objet de classe sera passé comme premier argument):

class Dummy(object):

    @classmethod
    def some_function(cls,*args,**kwargs):
        print cls

#both of these will have exactly the same effect
Dummy.some_function()
Dummy().some_function()

Sur l'utilisation de ceux-ci sur des instances : Il existe au moins deux utilisations principales pour appeler une méthode de classe sur une instance:

  1. self.some_function()appellera la version de some_functionsur le type réel de self, plutôt que sur la classe dans laquelle cet appel apparaît (et n'aura pas besoin d'attention si la classe est renommée); et
  2. Dans les cas où il some_functionest nécessaire d'implémenter un protocole, mais est utile d'appeler uniquement l'objet de classe.

La différence avecstaticmethod : Il existe une autre façon de définir des méthodes qui n'accèdent pas aux données d'instance, appelée staticmethod. Cela crée une méthode qui ne reçoit pas du tout un premier argument implicite; par conséquent, aucune information sur l'instance ou la classe sur laquelle elle a été appelée ne lui sera transmise.

In [6]: class Foo(object): some_static = staticmethod(lambda x: x+1)

In [7]: Foo.some_static(1)
Out[7]: 2

In [8]: Foo().some_static(1)
Out[8]: 2

In [9]: class Bar(Foo): some_static = staticmethod(lambda x: x*2)

In [10]: Bar.some_static(1)
Out[10]: 2

In [11]: Bar().some_static(1)
Out[11]: 2

La principale utilisation que j'en ai trouvée est d'adapter une fonction existante (qui ne s'attend pas à recevoir a self) pour qu'elle soit une méthode sur une classe (ou un objet).

Marcin
la source
2
Belle: j'aime que la réponse soit divisée en comment - pourquoi. Autant que je l'ai maintenant, @classmethod permet d'accéder à la fonction sans avoir besoin d'une instance. C'est exactement ce que je cherchais, merci.
mars
1
@marcin Le typage True Duck lui donne une autre dimension. Il s'agissait plutôt de ne pas écrire des choses comme self.foo()quand cela devrait être Bar.foo().
Voo
5
@Voo En fait, self.foo()c'est préférable, car il selfpeut s'agir d'une instance d'une sous-classe qui implémente la sienne foo.
Marcin
1
@Marcin Je pense que cela dépend de ce que vous voulez réaliser. Mais je comprends votre point.
mars
1
Vous devez utiliser un lambda différent pour Bar, comme 1+1 == 2 == 1*2, il est donc impossible de dire à partir des résultats affichés qu'il Bar().static_methodest réellement appelé.
George V. Reilly
0

En gros, vous devriez utiliser une @classmethod lorsque vous vous rendez compte que la définition de la méthode ne sera pas modifiée ou remplacée.

Un supplément: en théorie, les méthodes de classe sont plus rapides que les méthodes objet, car elles n'ont pas besoin d'être instanciées et nécessitent moins de mémoire.

Joao Barbosa
la source
-3

Si vous ajoutez le décorateur @classmethod, cela signifie que vous allez faire de cette méthode une méthode statique de java ou C ++. (la méthode statique est un terme général, je suppose ;) ) Python a également @staticmethod. et la différence entre la méthode de classe et la méthode statique est de savoir si vous pouvez accéder à la classe ou à la variable statique en utilisant l'argument ou le nom de classe lui-même.

class TestMethod(object):
    cls_var = 1
    @classmethod
    def class_method(cls):
        cls.cls_var += 1
        print cls.cls_var

    @staticmethod
    def static_method():
        TestMethod.cls_var += 1
        print TestMethod.cls_var
#call each method from class itself.
TestMethod.class_method()
TestMethod.static_method()

#construct instances
testMethodInst1 = TestMethod()    
testMethodInst2 = TestMethod()   

#call each method from instances
testMethodInst1.class_method()
testMethodInst2.static_method()

toutes ces classes augmentent cls.cls_var de 1 et l'impriment.

Et toutes les classes utilisant le même nom sur la même portée ou les mêmes instances construites avec ces classes vont partager ces méthodes. Il n'y a qu'un seul TestMethod.cls_var et aussi un seul TestMethod.class_method (), TestMethod.static_method ()

Et question importante. pourquoi cette méthode serait nécessaire.

classmethod ou staticmethod est utile lorsque vous créez cette classe en tant que fabrique ou lorsque vous ne devez initialiser votre classe qu'une seule fois. comme ouvrir le fichier une fois, et utiliser la méthode d'alimentation pour lire le fichier ligne par ligne.

Ryan Kim
la source
2
(a) Les méthodes statiques sont autre chose; (b) les méthodes de classe ne sont pas partagées par toutes les classes.
Marcin
@Marcin merci de m'avoir fait connaître mes définitions peu claires et tout ça.
Ryan Kim