variable - Quelles sont les méthodes de classe dans Python pour?




self python (10)

Je m'apprends Python et ma leçon la plus récente était que Python n'est pas Java , et donc j'ai juste passé un certain temps à transformer toutes mes méthodes de classe en fonctions.

Je réalise maintenant que je n'ai pas besoin d'utiliser les méthodes de classe pour ce que je ferais avec static méthodes static en Java, mais maintenant je ne suis pas sûr quand je les utiliserais. Tous les conseils que je peux trouver sur les méthodes de classe Python sont dans la lignée des débutants comme moi devraient éviter d'eux, et la documentation standard est à son plus opaque quand on en discute.

Est-ce que quelqu'un a un bon exemple d'utilisation d'une méthode Class en Python ou au moins quelqu'un peut-il me dire quand les méthodes de classe peuvent être raisonnablement utilisées?


C'est un sujet intéressant. Mon point de vue est que python classmethod fonctionne comme un singleton plutôt que comme une fabrique (qui retourne a produit une instance d'une classe). La raison pour laquelle il s'agit d'un singleton est qu'il y a un objet commun qui est produit (le dictionnaire) mais seulement une 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 motif d'usine tel que je le comprends. Ceci 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

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

Normalement cet objet doit être une instance mais le décorateur de la méthode @classmethod change 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 argyment 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 class dire class définition de class ). Si par conséquent, comme une surprise à un Rubyist, vous ne pouvez pas avoir une méthode de classe et une méthode d'instance avec le même nom .

Considère ceci:

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

Vous pouvez appeler foo sur une instance

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

Mais pas sur une 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)

Maintenant, ajoutez @classmethod :

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

Appeler une instance passe maintenant sa classe:

Foo().foo()
__main__.Foo

comme faire appel à une classe:

Foo.foo()
__main__.Foo

C'est seulement la convention qui dicte que nous utilisons self pour ce premier argument sur une méthode d'instance et cls sur une méthode de classe. Je n'ai pas utilisé ici pour illustrer que c'est juste un argument. Dans Ruby, self est 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 est juste 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 de @classmethod elle 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 ait piqué ma curiosité. Je suis loin d'être un expert en Python et aimerais avoir des commentaires si cela ne va pas.


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 n'utilisez pas @classmethod vous 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

J'avais l'habitude de travailler avec PHP et récemment je me demandais, que se passe-t-il avec ce classmethod? Le manuel de Python est très technique et très court dans les mots, donc cela ne vous aidera pas à comprendre cette fonctionnalité. J'étais googling et googling 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 plus bas. :)

en PHP (peut-être que vous ne connaissez pas 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, nous pouvons ajouter un décorateur @classmethod et ainsi il est possible d'avoir la sortie 10 et 20 respectivement. 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, n'est pas?


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

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 de procédure ci-dessus n'est pas bien organisé, comme vous pouvez le voir après seulement 3 modules, il devient confus, que fait chaque méthode? Vous pouvez utiliser de longs noms descriptifs pour les fonctions (comme dans Java) mais votre code devient 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 dans une classe pour les rendre plus spécifiques à une tâche que vous avez assigné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")

De cette façon est plus flexible et plus maintenable, vous regroupez des fonctions ensemble et c'est plus évident à ce que chaque fonction fait. 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.


Je voulais récemment 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 défini par programme. Mais je ne voulais pas instancier la classe chaque fois que je voulais sortir un message de débogage ou une erreur ou un avertissement. Mais je voulais aussi encapsuler le fonctionnement de cette installation de journalisation et la rendre réutilisable sans déclaration de globals.

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

Avec ma classe Logging simple, je pourrais 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 mises avec

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

Dans l'environnement "production", je peux spécifier

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 juste 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()

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 n'est pas associée à une instance particulière de la classe.

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

Répertoire actuel

  • Path.cwd()
    • Retourne le répertoire courant actuel; Par exemple, Path("/tmp/my_temp_dir") . C'est une méthode de classe.
  • .chdir()
    • Faire soi-même le répertoire actuel.

Comme le répertoire courant est large, la méthode cwd n'a pas d'instance particulière avec laquelle elle doit être associée. Cependant, changer le cwd dans le répertoire d'une instance Path donnée devrait en effet être une méthode d'instance.

Hmmm ... comme Path.cwd() renvoie effectivement une instance Path , je suppose que cela pourrait être considéré comme une méthode d'usine ...


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 pouvez utiliser la méthode de classe all() ou find() pour écrire User.all() ou User.find(firstname='Guido') . Cela pourrait être fait en utilisant des fonctions de niveau module bien sûr ...


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 admin peut vouloir supprimer un autre compte utilisateur, donc j'ai besoin d'instancier cet utilisateur et d'appeler sa méthode de suppression):

J'ai des méthodes de classe pour attraper 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()

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

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

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

Vous pouvez voir que la méthode dbapi() , qu'un backend de base de données utilise pour importer la bibliothèque de base de données spécifique au fournisseur dont elle a besoin, est une méthode de classe car elle doit être exécutée avant la création des instances de base de données. qu'il ne peut pas être une simple fonction ou une fonction statique, car il veut qu'il puisse appeler d'autres méthodes de support qui pourraient de même être écrites plus spécifiquement dans les sous-classes que dans leur classe parente. Et si vous envoyez une fonction ou une classe statique, vous "oubliez" et perdez la connaissance de la classe qui commence l'initialisation.





class-method