Quel est le but des méthodes de classe?

250

J'apprends moi-même Python et ma dernière leçon a été que Python n'est pas Java , et je viens donc de passer un peu de temps à transformer toutes mes méthodes de classe en fonctions.

Je me rends compte maintenant que je n'ai pas besoin d'utiliser des méthodes de classe pour ce que je ferais avec des staticméthodes en Java, mais maintenant je ne sais pas quand je les utiliserais. Tous les conseils que je peux trouver sur les méthodes de classe Python vont dans le sens des débutants comme moi devraient les éviter, et la documentation standard est la plus opaque lors de leur discussion.

Quelqu'un at-il un bon exemple d'utilisation d'une méthode de classe en Python ou au moins quelqu'un peut-il me dire quand les méthodes de classe peuvent être utilisées de manière raisonnable?

Dave Webb
la source

Réponses:

178

Les méthodes de classe sont quand vous avez besoin d'avoir des méthodes qui ne sont pas spécifiques à une instance particulière, mais impliquent toujours la classe d'une certaine manière. La chose la plus intéressante à leur sujet est qu'ils peuvent être remplacés par des sous-classes, ce qui n'est tout simplement pas possible dans les méthodes statiques de Java ou les fonctions de niveau module de Python.

Si vous avez une classe MyClasset une fonction au niveau du module qui fonctionne sur MyClass (usine, stub d'injection de dépendance, etc.), faites-en a classmethod. Ensuite, il sera disponible pour les sous-classes.

John Millikin
la source
Je vois. Mais, que diriez-vous si je veux simplement classer une méthode, qui n'a pas besoin de MyClass, mais qui a quand même du sens si je regroupe dans une autre MyClass? Je peux penser à le déplacer vers le module d'assistance si ..
swdev
J'ai une question liée à l'original, peut-être pourriez-vous y répondre en même temps? Quelle façon d'appeler les méthodes de classe d'un objet est "meilleure" ou "plus idiomatique": obj.cls_mthd(...)ou type(obj).cls_mthd(...)?
Alexey
63

Les méthodes d'usine (constructeurs alternatifs) sont en effet un exemple classique de méthodes de classe.

Fondamentalement, les méthodes de classe conviennent à chaque fois que vous souhaitez avoir une méthode qui s'intègre naturellement dans l'espace de noms de la classe, mais qui n'est pas associée à une instance particulière de la classe.

À titre d'exemple, dans l'excellent module unipath :

Répertoire actuel

  • Path.cwd()
    • Renvoie le répertoire actuel réel; par exemple Path("/tmp/my_temp_dir"). Il s'agit d'une méthode de classe.
  • .chdir()
    • Faites de vous le répertoire actuel.

Le répertoire courant étant à l'échelle du processus, la cwdméthode n'a pas d'instance particulière à laquelle il doit être associé. Cependant, le changement du cwdrépertoire d'une Pathinstance donnée devrait en effet être une méthode d'instance.

Hmmm ... comme Path.cwd()retourne en effet une Pathinstance, je suppose que cela pourrait être considéré comme une méthode d'usine ...

Wayne Werner
la source
1
Ouais ... les méthodes d'usine étaient la première chose qui me vint à l'esprit. Dans le même ordre d'idées, il y a des choses qui implémenteraient un modèle Singleton, comme: getAndOptionallyCreateSomeSingleInstanceOfSomeResource ()
Jemenake
3
En python, ce sont des méthodes statiques, pas des méthodes de classe.
Jaykul
46

Pensez-y de cette façon: les méthodes normales sont utiles pour masquer les détails de la répartition: vous pouvez taper myobj.foo()sans vous soucier de savoir si la foo()méthode est implémentée par la myobjclasse de l' objet ou l'une de ses classes parentes. Les méthodes de classe sont exactement analogues à cela, mais avec l'objet classe à la place: elles vous permettent d'appeler MyClass.foo()sans avoir à vous soucier de savoir si foo()est implémenté spécialement par MyClasscar il avait besoin de sa propre version spécialisée, ou s'il laisse sa classe parente gérer l'appel.

Les méthodes de classe sont essentielles lorsque vous effectuez une configuration ou un calcul qui précède la création d'une instance réelle, car tant que l'instance n'existe pas, vous ne pouvez évidemment pas utiliser l'instance comme point de répartition pour vos appels de méthode. Un bon exemple peut être consulté dans le code source de SQLAlchemy; jetez un oeil à la dbapi()méthode de classe sur le lien suivant:

https://github.com/zzzeek/sqlalchemy/blob/ab6946769742602e40fb9ed9dde5f642885d1906/lib/sqlalchemy/dialects/mssql/pymssql.py#L47

Vous pouvez voir que la dbapi()méthode, qu'un backend de base de données utilise pour importer la bibliothèque de bases de données spécifique au fournisseur dont il a besoin à la demande, est une méthode de classe car elle doit s'exécuter avant que les instances d'une connexion de base de données particulière ne soient créées - mais qu'elle ne peut pas être une fonction simple ou une fonction statique, car ils veulent qu'elle puisse appeler d'autres méthodes de support qui pourraient également devoir être écrites plus spécifiquement dans des sous-classes que dans leur classe parente. Et si vous effectuez une répartition vers une fonction ou une classe statique, vous "oubliez" et perdez la connaissance de la classe qui effectue l'initialisation.

Brandon Rhodes
la source
Lien cassé, veuillez ajuster
piertoni
28

J'ai récemment voulu une classe de journalisation très légère qui produirait des quantités variables de sortie en fonction du niveau de journalisation qui pourrait être programmé. Mais je ne voulais pas instancier la classe chaque fois que je voulais afficher un message de débogage ou une erreur ou un avertissement. Mais je voulais aussi encapsuler le fonctionnement de cette fonction de journalisation et la rendre réutilisable sans la déclaration d'aucun global.

J'ai donc utilisé des variables de classe et le @classmethoddécorateur pour y parvenir.

Avec ma classe de journalisation simple, je pouvais faire ce qui suit:

Logger._level = Logger.DEBUG

Ensuite, dans mon code, si je voulais cracher un tas d'informations de débogage, je devais simplement coder

Logger.debug( "this is some annoying message I only want to see while debugging" )

Des erreurs pourraient être éliminées avec

Logger.error( "Wow, something really awful happened." )

Dans l'environnement "production", je peux préciser

Logger._level = Logger.ERROR

et maintenant, seul le message d'erreur sera affiché. Le message de débogage ne sera pas imprimé.

Voici ma classe:

class Logger :
    ''' Handles logging of debugging and error messages. '''

    DEBUG = 5
    INFO  = 4
    WARN  = 3
    ERROR = 2
    FATAL = 1
    _level = DEBUG

    def __init__( self ) :
        Logger._level = Logger.DEBUG

    @classmethod
    def isLevel( cls, level ) :
        return cls._level >= level

    @classmethod
    def debug( cls, message ) :
        if cls.isLevel( Logger.DEBUG ) :
            print "DEBUG:  " + message

    @classmethod
    def info( cls, message ) :
        if cls.isLevel( Logger.INFO ) :
            print "INFO :  " + message

    @classmethod
    def warn( cls, message ) :
        if cls.isLevel( Logger.WARN ) :
            print "WARN :  " + message

    @classmethod
    def error( cls, message ) :
        if cls.isLevel( Logger.ERROR ) :
            print "ERROR:  " + message

    @classmethod
    def fatal( cls, message ) :
        if cls.isLevel( Logger.FATAL ) :
            print "FATAL:  " + message

Et du code qui le teste un peu:

def logAll() :
    Logger.debug( "This is a Debug message." )
    Logger.info ( "This is a Info  message." )
    Logger.warn ( "This is a Warn  message." )
    Logger.error( "This is a Error message." )
    Logger.fatal( "This is a Fatal message." )

if __name__ == '__main__' :

    print "Should see all DEBUG and higher"
    Logger._level = Logger.DEBUG
    logAll()

    print "Should see all ERROR and higher"
    Logger._level = Logger.ERROR
    logAll()
Marvo
la source
10
Cela ne me semble pas être un bon exemple de méthode de classe, car cela aurait pu être fait de manière moins complexe en faisant simplement toutes les fonctions de niveau module des méthodes de classe et en supprimant complètement la classe.
martineau
10
Oh, plus important encore, Python fournit une classe de journalisation très simple à utiliser. Alors pire que de créer une solution moins qu'optimale, j'ai réinventé la roue. Double claque à la main. Mais il fournit au moins un exemple de travail. Je ne suis pas sûr d'être d'accord pour me débarrasser de la classe, cependant, au niveau conceptuel. Parfois, vous voulez encapsuler du code pour une réutilisation facile, et les méthodes de journalisation sont une excellente cible pour la réutilisation.
2010
3
Pourquoi n'utilisez-vous pas une méthode statique?
bdforbes
26

Les constructeurs alternatifs en sont l'exemple classique.

Aaron Maenpaa
la source
11

Lorsqu'un utilisateur se connecte sur mon site Web, un objet User () est instancié à partir du nom d'utilisateur et du mot de passe.

Si j'ai besoin d'un objet utilisateur sans que l'utilisateur soit là pour se connecter (par exemple, un utilisateur administrateur peut vouloir supprimer un autre compte d'utilisateurs, j'ai donc besoin d'instancier cet utilisateur et d'appeler sa méthode de suppression):

J'ai des méthodes de classe pour saisir l'objet utilisateur.

class User():
    #lots of code
    #...
    # more code

    @classmethod
    def get_by_username(cls, username):
        return cls.query(cls.username == username).get()

    @classmethod
    def get_by_auth_id(cls, auth_id):
        return cls.query(cls.auth_id == auth_id).get()
Robert King
la source
9

Je pense que la réponse la plus claire est celle d' AmanKow . Cela se résume à la façon dont vous souhaitez organiser votre code. Vous pouvez tout écrire en tant que fonctions de niveau module qui sont enveloppées dans l'espace de noms du module, c'est-à-dire

module.py (file 1)
---------
def f1() : pass
def f2() : pass
def f3() : pass


usage.py (file 2)
--------
from module import *
f1()
f2()
f3()
def f4():pass 
def f5():pass

usage1.py (file 3)
-------------------
from usage import f4,f5
f4()
f5()

Le code procédural ci-dessus n'est pas bien organisé, comme vous pouvez le voir après seulement 3 modules, cela devient confus, que fait chaque méthode? Vous pouvez utiliser des noms descriptifs longs pour les fonctions (comme en java) mais votre code devient néanmoins ingérable très rapidement.

La méthode orientée objet consiste à décomposer votre code en blocs gérables, c'est-à-dire que les classes, les objets et les fonctions peuvent être associés à des instances d'objets ou à des classes.

Avec les fonctions de classe, vous gagnez un autre niveau de division dans votre code par rapport aux fonctions de niveau module. Vous pouvez donc regrouper des fonctions connexes au sein d'une classe pour les rendre plus spécifiques à une tâche que vous avez affectée à cette classe. Par exemple, vous pouvez créer une classe d'utilitaire de fichier:

class FileUtil ():
  def copy(source,dest):pass
  def move(source,dest):pass
  def copyDir(source,dest):pass
  def moveDir(source,dest):pass

//usage
FileUtil.copy("1.txt","2.txt")
FileUtil.moveDir("dir1","dir2")

Cette façon est plus flexible et plus maintenable, vous regroupez les fonctions ensemble et c'est plus évident pour ce que fait chaque fonction. Vous évitez également les conflits de noms, par exemple la copie de fonction peut exister dans un autre module importé (par exemple copie réseau) que vous utilisez dans votre code, donc lorsque vous utilisez le nom complet FileUtil.copy () vous supprimez le problème et les deux fonctions de copie peut être utilisé côte à côte.

firephil
la source
1
Pour utiliser f1 (), f2 () et ainsi de suite, comme vous l'avez fait, ne devez-vous pas utiliser depuis module import * et depuis import import *?
Jblasco
@Jblasco J'ai corrigé les instructions d'importation pour éliminer la confusion, oui si vous importez le module, vous devez préfixer les fonctions avec le nom du module. ie module d'importation -> module.f1 () etc
firephil
Je ne suis pas d'accord avec cette réponse; Python n'est pas Java. La question du code ingérable ne se pose qu'apparemment en raison du choix délibéré de mauvais noms dans l'exemple. Si à la place vous répliquiez les FileUtilméthodes de classe en tant que fonctions d'un file_utilmodule, ce serait comparable et n'abuseriez pas de la POO quand aucun objet n'existe réellement (en fait, vous pourriez dire que c'est préférable, car vous ne vous retrouvez pas avec from file_util import FileUtilou autre verbosité). Les conflits de noms peuvent également être évités dans le code de procédure en faisant import file_utilau lieu de from file_util import ....
ForgottenUmbrella
7

Honnêtement? Je n'ai jamais trouvé d'utilisation pour la méthode statique ou la méthode de classe. Je n'ai pas encore vu d'opération qui ne peut pas être effectuée à l'aide d'une fonction globale ou d'une méthode d'instance.

Ce serait différent si python utilisait des membres privés et protégés plus comme Java. En Java, j'ai besoin d'une méthode statique pour pouvoir accéder aux membres privés d'une instance pour faire des choses. En Python, c'est rarement nécessaire.

Habituellement, je vois des gens utiliser des méthodes statiques et des méthodes de classe quand tout ce qu'ils ont vraiment besoin de faire est de mieux utiliser les espaces de noms au niveau du module de python.

Jason Baker
la source
1
Privé: _variable_name et protégé: __variable_name
Bradley Kreider
1
Une utilisation indispensable est setUpClass et tearDownClass de unittest . Vous utilisez des tests, non? :)
dbn
7

Il vous permet d'écrire des méthodes de classe génériques que vous pouvez utiliser avec n'importe quelle classe compatible.

Par exemple:

@classmethod
def get_name(cls):
    print cls.name

class C:
    name = "tester"

C.get_name = get_name

#call it:
C.get_name()

Si vous ne l'utilisez pas, @classmethodvous pouvez le faire avec le mot-clé self mais il a besoin d'une instance de Class:

def get_name(self):
    print self.name

class C:
    name = "tester"

C.get_name = get_name

#call it:
C().get_name() #<-note the its an instance of class C
encore
la source
5

J'avais l'habitude de travailler avec PHP et récemment je me demandais, que se passe-t-il avec cette méthode de classe? Le manuel de Python est très technique et très court en mots, donc il n'aidera pas à comprendre cette fonctionnalité. J'étais sur Google et sur Google et j'ai trouvé la réponse -> http://code.anjanesh.net/2007/12/python-classmethods.html .

Si vous êtes paresseux de cliquer dessus. Mon explication est plus courte et ci-dessous. :)

en PHP (vous ne connaissez peut-être pas tous PHP, mais ce langage est si simple que tout le monde devrait comprendre de quoi je parle), nous avons des variables statiques comme ceci:


class A
{

    static protected $inner_var = null;

    static public function echoInnerVar()
    {
        echo self::$inner_var."\n";
    }

    static public function setInnerVar($v)
    {
        self::$inner_var = $v;
    }

}

class B extends A
{
}

A::setInnerVar(10);
B::setInnerVar(20);

A::echoInnerVar();
B::echoInnerVar();

La sortie sera dans les deux cas 20.

Cependant en python on peut ajouter @classmethod decorator et ainsi il est possible d'avoir respectivement les sorties 10 et 20. Exemple:


class A(object):
    inner_var = 0

    @classmethod
    def setInnerVar(cls, value):
        cls.inner_var = value

    @classmethod
    def echoInnerVar(cls):
        print cls.inner_var


class B(A):
    pass


A.setInnerVar(10)
B.setInnerVar(20)

A.echoInnerVar()
B.echoInnerVar()

Intelligent, non?

Drachenfels
la source
Un problème potentiel avec votre exemple Python est que si a B.setInnerVar(20)été 10omis , il s'imprimerait deux fois (plutôt que de donner une erreur sur le deuxième appel echoInnerBar () qui n'a pas inner_varété défini.
martineau
5

Les méthodes de classe fournissent un «sucre sémantique» (je ne sais pas si ce terme est largement utilisé) - ou «commodité sémantique».

Exemple: vous avez un ensemble de classes représentant des objets. Vous voudrez peut-être avoir la méthode de classe all()ou find()écrire User.all()ou User.find(firstname='Guido'). Cela pourrait être fait en utilisant des fonctions de niveau module bien sûr ...

Pierre
la source
Votre exemple suppose, bien sûr, que la classe garde une trace de toutes ses instances et peut y accéder après leur création - ce qui n'est pas fait automatiquement.
martineau
1
"sucre sémantique" sonne comme une belle correspondance avec "sucre syntaxique".
XTL
3

Ce qui vient de me frapper, venant de Ruby, est qu'une méthode dite de classe et une méthode dite d' instance sont juste une fonction avec une signification sémantique appliquée à son premier paramètre, qui est silencieusement transmise lorsque la fonction est appelée comme méthode de un objet (ie obj.meth()).

Normalement, cet objet doit être une instance mais le @classmethod décorateur de méthode modifie les règles pour passer une classe. Vous pouvez appeler une méthode de classe sur une instance (c'est juste une fonction) - le premier argument sera sa classe.

Parce que c'est juste une fonction , elle ne peut être déclarée qu'une seule fois dans une portée donnée (c'est-à-dire une classdéfinition). Il s'ensuit donc, comme une surprise pour un Rubyist, que vous ne pouvez pas avoir une méthode de classe et une méthode d'instance du même nom .

Considère ceci:

class Foo():
  def foo(x):
    print(x)

Vous pouvez faire appel fooà une instance

Foo().foo()
<__main__.Foo instance at 0x7f4dd3e3bc20>

Mais pas en classe:

Foo.foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method foo() must be called with Foo instance as first argument (got nothing instead)

Ajoutez maintenant @classmethod:

class Foo():
  @classmethod
  def foo(x):
    print(x)

L'appel à une instance passe désormais sa classe:

Foo().foo()
__main__.Foo

tout comme faire appel à une classe:

Foo.foo()
__main__.Foo

C'est seulement la convention qui dicte que nous utilisons selfpour ce premier argument une méthode d'instance et clsune méthode de classe. Je n'ai utilisé ni l'un ni l'autre ici pour illustrer que ce n'est qu'un argument. En Ruby, selfest un mot - clé.

Contraste avec Ruby:

class Foo
  def foo()
    puts "instance method #{self}"
  end
  def self.foo()
    puts "class method #{self}"
  end
end

Foo.foo()
class method Foo

Foo.new.foo()
instance method #<Foo:0x000000020fe018>

La méthode de classe Python n'est qu'une fonction décorée et vous pouvez utiliser les mêmes techniques pour créer vos propres décorateurs . Une méthode décorée enveloppe la méthode réelle (dans le cas où @classmethodelle passe l'argument de classe supplémentaire). La méthode sous-jacente est toujours là, cachée mais toujours accessible .


note de bas de page: j'ai écrit ceci après qu'un conflit de noms entre une classe et une méthode d'instance a piqué ma curiosité. Je suis loin d'être un expert Python et j'aimerais avoir des commentaires si tout cela est faux.

étoilé
la source
"qui passe silencieusement lorsque la fonction est appelée comme méthode d'un objet" - je crois que ce n'est pas exact. AFICT, rien n'est "passé silencieusement" en Python. OMI, Python est beaucoup plus sensible que Ruby dans ce sens (sauf pour super()). AFICT, la "magie" se produit lorsqu'un attribut est défini ou lu. L'appel obj.meth(arg)en Python (contrairement à Ruby) signifie simplement (obj.meth)(arg). Rien n'est transmis silencieusement n'importe où, obj.methc'est juste un objet appelable qui accepte un argument de moins que la fonction à partir de laquelle il a été créé.
Alexey
obj.meth, à son tour, signifie simplement getattr(obj, "meth").
Alexey
Il est logique d'avoir une réponse adaptée aux personnes venant d'autres langues courantes. Cependant, une chose qui manque à cette conversation, qui la relie, est la notion de descripteur Python . Les fonctions sont des descripteurs , et c'est ainsi que le premier argument passe "automatiquement" à une instance lorsqu'une fonction est un attribut de la classe. C'est aussi la façon dont les méthodes de classe sont implémentées, et une implémentation pure python est fournie dans le HOWTO que j'ai lié. Le fait qu'il soit également décorateur est en quelque sorte accessoire
juanpa.arrivillaga
2

C'est un sujet intéressant. Mon point de vue est que la méthode de classe python fonctionne comme un singleton plutôt qu'une usine (qui renvoie une instance produite d'une classe). La raison pour laquelle il s'agit d'un singleton est qu'il existe un objet commun qui est produit (le dictionnaire) mais une seule fois pour la classe mais partagé par toutes les instances.

Pour illustrer ceci, voici un exemple. Notez que toutes les instances ont une référence au dictionnaire unique. Ce n'est pas un modèle d'usine tel que je le comprends. C'est probablement très unique à python.

class M():
 @classmethod
 def m(cls, arg):
     print "arg was",  getattr(cls, "arg" , None),
     cls.arg = arg
     print "arg is" , cls.arg

 M.m(1)   # prints arg was None arg is 1
 M.m(2)   # prints arg was 1 arg is 2
 m1 = M()
 m2 = M() 
 m1.m(3)  # prints arg was 2 arg is 3  
 m2.m(4)  # prints arg was 3 arg is 4 << this breaks the factory pattern theory.
 M.m(5)   # prints arg was 4 arg is 5
Peter Moore
la source
2

Je me posais plusieurs fois la même question. Et même si les gars ici ont essayé de l'expliquer, à mon humble avis la meilleure réponse (et la plus simple) que j'ai trouvée est la description de la méthode Class dans la documentation Python.

Il est également fait référence à la méthode statique. Et dans le cas où quelqu'un connaît déjà les méthodes d'instance (ce que je suppose), cette réponse pourrait être la dernière pièce pour tout rassembler ...

Des explications plus approfondies sur ce sujet sont également disponibles dans la documentation: La hiérarchie des types standard (faites défiler jusqu'à la section Méthodes d'instance )

Dee'Kej
la source
1

@classmethodpeut être utile pour instancier facilement des objets de cette classe à partir de ressources externes. Considérer ce qui suit:

import settings

class SomeClass:
    @classmethod
    def from_settings(cls):
        return cls(settings=settings)

    def __init__(self, settings=None):
        if settings is not None:
            self.x = settings['x']
            self.y = settings['y']

Puis dans un autre fichier:

from some_package import SomeClass

inst = SomeClass.from_settings()

L'accès à inst.x donnera la même valeur que les paramètres ['x'].

psilocybine
la source
0

Une classe définit un ensemble d'instances, bien sûr. Et les méthodes d'une classe fonctionnent sur les instances individuelles. Les méthodes de classe (et les variables) permettent de suspendre d'autres informations liées à l'ensemble d'instances.

Par exemple, si votre classe définit un ensemble d'élèves, vous souhaiterez peut-être des variables ou des méthodes de classe qui définissent des éléments tels que l'ensemble de notes auquel les élèves peuvent appartenir.

Vous pouvez également utiliser des méthodes de classe pour définir des outils pour travailler sur l'ensemble complet. Par exemple, Student.all_of_em () peut renvoyer tous les étudiants connus. Évidemment, si votre ensemble d'instances a plus de structure qu'un simple ensemble, vous pouvez fournir des méthodes de classe pour connaître cette structure. Students.all_of_em (grade = 'juniors')

Des techniques comme celle-ci ont tendance à conduire au stockage des membres de l'ensemble d'instances dans des structures de données ancrées dans des variables de classe. Vous devez alors prendre soin d'éviter de frustrer la collecte des ordures.

Ben Hyde
la source