web-dev-qa-db-fra.com

Signification de @classmethod et @staticmethod pour débutant?

Quelqu'un pourrait-il m'expliquer le sens de @classmethod et @staticmethod en python? J'ai besoin de connaître la différence et le sens.

Autant que je sache, @classmethod indique à une classe que c'est une méthode qui devrait être héritée dans des sous-classes, ou ... quelque chose. Cependant, quel est le point de cela? Pourquoi ne pas simplement définir la méthode de classe sans ajouter @classmethod ou @staticmethod ni aucune définition de @?

tl; dr: quand devrais-je les utiliser, pourquoi dois-je les utiliser et comment dois-je les utiliser?

Je suis assez avancé en C++, donc utiliser des concepts de programmation plus avancés ne devrait pas poser de problème. N'hésitez pas à me donner un exemple C++ correspondant, si possible.

1493
user1632861

Bien que classmethod et staticmethod soient assez similaires, il existe une légère différence d'utilisation entre les deux entités: classmethod doit avoir une référence à un objet de classe comme premier paramètre, tandis que staticmethod peut avoir aucun paramètre du tout.

Exemple

class Date(object):

    def __init__(self, day=0, month=0, year=0):
        self.day = day
        self.month = month
        self.year = year

    @classmethod
    def from_string(cls, date_as_string):
        day, month, year = map(int, date_as_string.split('-'))
        date1 = cls(day, month, year)
        return date1

    @staticmethod
    def is_date_valid(date_as_string):
        day, month, year = map(int, date_as_string.split('-'))
        return day <= 31 and month <= 12 and year <= 3999

date2 = Date.from_string('11-09-2012')
is_date = Date.is_date_valid('11-09-2012')

Explication

Supposons un exemple de classe traitant des informations de date (ce sera notre standard):

class Date(object):

    def __init__(self, day=0, month=0, year=0):
        self.day = day
        self.month = month
        self.year = year

Cette classe pourrait évidemment être utilisée pour stocker des informations sur certaines dates (sans information de fuseau horaire; supposons que toutes les dates soient présentées en UTC).

Ici nous avons __init__, un initialiseur typique de Python instances de classe, qui reçoit les arguments comme un instancemethod typique, ayant le premier argument non optionnel (self) qui contient une référence à une instance nouvellement créée.

Méthode de classe

Nous avons certaines tâches qui peuvent être bien effectuées en utilisant classmethods.

Supposons que nous voulions créer un grand nombre d'instances de classe Date dont les informations de date proviennent d'une source externe codée sous forme de chaîne au format 'jj-mm-aaaa'. Supposons que nous devions le faire à différents endroits du code source de notre projet.

Donc, ce que nous devons faire ici est:

  1. Analyser une chaîne pour recevoir le jour, le mois et l’année sous forme de trois variables entières ou d’un tuple à 3 éléments constitué de cette variable.
  2. Instanciez Date en transmettant ces valeurs à l'appel d'initialisation.

Cela ressemblera à:

day, month, year = map(int, string_date.split('-'))
date1 = Date(day, month, year)

Pour cela, C++ peut implémenter une telle fonctionnalité avec surcharge, mais Python ne possède pas cette surcharge. Au lieu de cela, nous pouvons utiliser classmethod. Créons un autre " constructeur ".

    @classmethod
    def from_string(cls, date_as_string):
        day, month, year = map(int, date_as_string.split('-'))
        date1 = cls(day, month, year)
        return date1

date2 = Date.from_string('11-09-2012')

Examinons plus attentivement l'implémentation ci-dessus et examinons les avantages que nous avons ici:

  1. Nous avons implémenté l’analyse de chaîne de date à un endroit et il est maintenant réutilisable.
  2. L'encapsulation fonctionne bien ici (si vous pensez que vous pouvez implémenter l'analyse de chaînes en tant que fonction unique, cette solution convient beaucoup mieux au paradigme OOP.).
  3. cls est un objet qui contient le la classe elle-même, pas une instance de la classe. C'est plutôt cool car si nous héritons de notre classe Date, tous les enfants auront également from_string défini.

Méthode statique

Qu'en est-il de staticmethod? C'est assez similaire à classmethod mais ne prend aucun paramètre obligatoire (comme le font les méthodes class ou d'instance).

Regardons le cas d'utilisation suivant.

Nous avons une chaîne de date que nous voulons valider d'une manière ou d'une autre. Cette tâche est également liée de manière logique à la classe Date que nous avons utilisée jusqu'à présent, mais ne nécessite pas d'instanciation de celle-ci.

Voici où staticmethod peut être utile. Regardons le morceau de code suivant:

    @staticmethod
    def is_date_valid(date_as_string):
        day, month, year = map(int, date_as_string.split('-'))
        return day <= 31 and month <= 12 and year <= 3999

    # usage:
    is_date = Date.is_date_valid('11-09-2012')

Donc, comme on peut le voir avec l'utilisation de staticmethod, nous n'avons aucun accès à ce que la classe est --- c'est en fait juste une fonction, appelée syntaxiquement comme une méthode, mais sans accès à l'objet et à ses les internes (champs et autres méthodes), alors que classmethod le fait.

2469
Rostyslav Dzinko

La réponse de Rostyslav Dzinko est très appropriée. Je pensais pouvoir mettre en évidence une autre raison pour laquelle vous devriez choisir @classmethod par rapport à @staticmethod lorsque vous créez un constructeur supplémentaire.

Dans l'exemple ci-dessus, Rostyslav a utilisé le @classmethodfrom_string en tant qu'usine pour créer des objets Date à partir de paramètres inacceptables. La même chose peut être faite avec @staticmethod comme le montre le code ci-dessous:

class Date:
  def __init__(self, month, day, year):
    self.month = month
    self.day   = day
    self.year  = year


  def display(self):
    return "{0}-{1}-{2}".format(self.month, self.day, self.year)


  @staticmethod
  def millenium(month, day):
    return Date(month, day, 2000)

new_year = Date(1, 1, 2013)               # Creates a new Date object
millenium_new_year = Date.millenium(1, 1) # also creates a Date object. 

# Proof:
new_year.display()           # "1-1-2013"
millenium_new_year.display() # "1-1-2000"

isinstance(new_year, Date) # True
isinstance(millenium_new_year, Date) # True

Ainsi, new_year et millenium_new_year sont des instances de la classe Date.

Mais, si vous observez de près, le processus Factory est codé en dur pour créer des objets Date quoi qu'il en soit. Cela signifie que même si la classe Date est sous-classe, les sous-classes continueront de créer un objet Date simple (sans aucune propriété de la sous-classe). Voir cela dans l'exemple ci-dessous:

class DateTime(Date):
  def display(self):
      return "{0}-{1}-{2} - 00:00:00PM".format(self.month, self.day, self.year)


datetime1 = DateTime(10, 10, 1990)
datetime2 = DateTime.millenium(10, 10)

isinstance(datetime1, DateTime) # True
isinstance(datetime2, DateTime) # False

datetime1.display() # returns "10-10-1990 - 00:00:00PM"
datetime2.display() # returns "10-10-2000" because it's not a DateTime object but a Date object. Check the implementation of the millenium method on the Date class

datetime2 n'est pas une instance de DateTime? WTF? Eh bien, c'est à cause du @staticmethod décorateur utilisé.

Dans la plupart des cas, cela est indésirable. Si vous voulez une méthode Factory qui connaisse la classe qui l'a appelée, alors @classmethod est ce dont vous avez besoin.

Réécriture du Date.millenium en tant que (c'est la seule partie du code ci-dessus qui change)

@classmethod
def millenium(cls, month, day):
    return cls(month, day, 2000)

s'assure que le class n'est pas codé en dur mais plutôt appris. cls peut être n'importe quelle sous-classe. Le object résultant sera à juste titre une instance de cls. Testons cela.

datetime1 = DateTime(10, 10, 1990)
datetime2 = DateTime.millenium(10, 10)

isinstance(datetime1, DateTime) # True
isinstance(datetime2, DateTime) # True


datetime1.display() # "10-10-1990 - 00:00:00PM"
datetime2.display() # "10-10-2000 - 00:00:00PM"

La raison en est que, comme vous le savez maintenant, @classmethod a été utilisé à la place de @staticmethod

807
Yaw Boakye

@classmethod signifie: lorsque cette méthode est appelée, nous passons la classe comme premier argument à la place de l'instance de cette classe (comme nous le faisons normalement avec des méthodes). Cela signifie que vous pouvez utiliser la classe et ses propriétés dans cette méthode plutôt que dans une instance particulière.

@staticmethod signifie: lorsque cette méthode est appelée, nous ne lui passons pas une instance de la classe (comme nous le faisons normalement avec des méthodes). Cela signifie que vous pouvez insérer une fonction dans une classe, mais que vous ne pouvez pas accéder à l'instance de cette classe (utile lorsque votre méthode n'utilise pas l'instance).

235
Simeon Visser

Quand utiliser chacun

@staticmethod La fonction n'est rien de plus qu'une fonction définie dans une classe. Il est appelable sans instancier d'abord la classe. Sa définition est immuable par héritage.

  • Python n'a pas besoin d'instancier une méthode liée pour un objet.
  • Cela facilite la lisibilité du code: en voyant @ staticmethod , nous savons que la méthode ne dépend pas de l'état de l'objet lui-même;

La fonction @classmethod est également appelable sans instancier la classe, mais sa définition suit la classe Sub, et non la classe Parent, via l'héritage, pouvant être remplacée par la sous-classe. C’est parce que le premier argument de la fonction @classmethod doit toujours être cls (class).

  • Méthodes d'usine , utilisées pour créer une instance pour une classe en utilisant par exemple une sorte de pré-traitement.
  • Méthodes statiques appelant des méthodes statiques : si vous divisez une méthode statique en plusieurs méthodes statiques, vous ne devez pas coder en dur le nom de la classe, mais utiliser des méthodes de classe.

ici est un bon lien vers ce sujet.

71
zangw

Signification de @classmethod et @staticmethod?

  • Une méthode est une fonction dans l'espace de noms d'un objet, accessible en tant qu'attribut.
  • Une méthode régulière (par exemple) obtient l'instance (nous l'appelons généralement self) en tant que premier argument implicite.
  • Une méthode class obtient la classe (nous l'appelons généralement cls) en tant que premier argument implicite.
  • Une méthode static n'obtient aucun premier argument implicite (comme une fonction régulière).

quand devrais-je les utiliser, pourquoi devrais-je les utiliser et comment les utiliser?

Vous n'avez pas besoin ni de décorateur. Mais sur le principe que vous devez minimiser le nombre d’arguments des fonctions (voir Clean Coder), ils sont utiles pour cela.

class Example(object):

    def regular_instance_method(self):
        """A function of an instance has access to every attribute of that 
        instance, including its class (and its attributes.)
        Not accepting at least one argument is a TypeError.
        Not understanding the semantics of that argument is a user error.
        """
        return some_function_f(self)

    @classmethod
    def a_class_method(cls):
        """A function of a class has access to every attribute of the class.
        Not accepting at least one argument is a TypeError.
        Not understanding the semantics of that argument is a user error.
        """
        return some_function_g(cls)

    @staticmethod
    def a_static_method():
        """A static method has no information about instances or classes
        unless explicitly given. It just lives in the class (and thus its 
        instances') namespace.
        """
        return some_function_h()

Pour les méthodes d'instance et les méthodes de classe, ne pas accepter au moins un argument est une erreur TypeError, mais ne pas comprendre la sémantique de cet argument est une erreur de l'utilisateur.

(Définissez some_function ', par exemple:

some_function_h = some_function_g = some_function_f = lambda x=None: x

et cela fonctionnera.)

recherches pointillées sur les instances et les classes:

Une recherche en pointillés sur une instance est effectuée dans cet ordre - nous recherchons:

  1. un descripteur de données dans l'espace de noms de classe (comme une propriété)
  2. données dans l'instance __dict__
  3. un descripteur de non-données dans l'espace de noms de classe (méthodes).

Remarque, une recherche en pointillé sur une instance est appelée comme ceci:

instance = Example()
instance.regular_instance_method 

et les méthodes sont des attributs appelables:

instance.regular_instance_method()

méthodes d'instance

L'argument, self, est donné implicitement via la recherche en pointillé.

Vous devez accéder aux méthodes d'instance à partir d'instances de la classe.

>>> instance = Example()
>>> instance.regular_instance_method()
<__main__.Example object at 0x00000000399524E0>

méthodes de classe

L'argument, cls, est donné implicitement via une recherche en pointillé.

Vous pouvez accéder à cette méthode via une instance ou la classe (ou sous-classes).

>>> instance.a_class_method()
<class '__main__.Example'>
>>> Example.a_class_method()
<class '__main__.Example'>

méthodes statiques

Aucun argument n'est implicitement donné. Cette méthode fonctionne comme n'importe quelle fonction définie (par exemple) sur un espace de noms de modules, sauf qu'elle peut être recherchée.

>>> print(instance.a_static_method())
None

Encore une fois, quand devrais-je les utiliser, pourquoi devrais-je les utiliser?

Chacune de ces méthodes est progressivement plus restrictive dans les informations transmises à la méthode par rapport à la méthode par instance.

Utilisez-les lorsque vous n'avez pas besoin de l'information.

Cela rend vos fonctions et méthodes plus faciles à raisonner et à unifier.

Quel est le plus facile de raisonner?

def function(x, y, z): ...

ou

def function(y, z): ...

ou

def function(z): ...

Les fonctions avec moins d'arguments sont plus faciles à raisonner. Ils sont également plus faciles à unittest.

Celles-ci s'apparentent à des méthodes d'instance, de classe et statique. Gardant à l'esprit que lorsque nous avons un exemple, nous avons aussi sa classe, encore une fois, demandez-vous quelle est la plus facile à raisonner?

def an_instance_method(self, arg, kwarg=None):
    cls = type(self)             # Also has the class of instance!
    ...

@classmethod
def a_class_method(cls, arg, kwarg=None):
    ...

@staticmethod
def a_static_method(arg, kwarg=None):
    ...

Exemples intégrés

Voici quelques exemples classiques que je préfère:

La méthode statique str.maketrans était une fonction du module string, mais il est beaucoup plus pratique qu'elle soit accessible à partir de l'espace de nom str.

>>> 'abc'.translate(str.maketrans({'a': 'b'}))
'bbc'

La méthode de classe dict.fromkeys renvoie un nouveau dictionnaire instancié à partir d'un itérable de clés:

>>> dict.fromkeys('abc')
{'a': None, 'c': None, 'b': None}

Lorsqu'il est sous-classé, nous voyons qu'il récupère les informations de classe en tant que méthode de classe, ce qui est très utile:

>>> class MyDict(dict): pass
>>> type(MyDict.fromkeys('abc'))
<class '__main__.MyDict'> 

Mon conseil - Conclusion

Utilisez des méthodes statiques lorsque vous n'avez pas besoin des arguments de classe ou d'instance, mais que la fonction est liée à l'utilisation de l'objet et qu'il est pratique que la fonction se trouve dans l'espace de noms de l'objet.

Utilisez des méthodes de classe lorsque vous n'avez pas besoin d'informations d'instance, mais que vous avez besoin des informations de classe, peut-être pour son autre classe ou ses méthodes statiques, ou peut-être en tant que constructeur. (Vous ne coderiez pas la classe de manière à ce que les sous-classes puissent être utilisées ici.)

39
Aaron Hall

On utiliserait @classmethod quand il/elle voudrait changer le comportement de la méthode en fonction de la sous-classe qui appelle la méthode. Rappelez-vous que nous avons une référence à la classe appelante dans une méthode de classe.

En utilisant statique, vous voudriez que le comportement reste inchangé dans toutes les sous-classes

Exemple:

class Hero:

  @staticmethod
  def say_hello():
     print("Helllo...")

  @classmethod
  def say_class_hello(cls):
     if(cls.__name__=="HeroSon"):
        print("Hi Kido")
     Elif(cls.__name__=="HeroDaughter"):
        print("Hi Princess")

class HeroSon(Hero):
  def say_son_hello(self):
     print("test  hello")



class HeroDaughter(Hero):
  def say_daughter_hello(self):
     print("test  hello daughter")


testson = HeroSon()

testson.say_class_hello() #Output: "Hi Kido"

testson.say_hello() #Outputs: "Helllo..."

testdaughter = HeroDaughter()

testdaughter.say_class_hello() #Outputs: "Hi Princess"

testdaughter.say_hello() #Outputs: "Helllo..."
34
Krishnendu Kunti

Une petite compilation

@ staticmethod Un moyen d'écrire une méthode dans une classe sans faire référence à l'objet sur lequel elle est appelée. Donc pas besoin de passer des arguments implicites comme self ou cls. Elle est écrite exactement de la même manière que celle utilisée en dehors de la classe, mais elle n’a aucune utilité dans python, car si vous devez encapsuler une méthode dans une classe, celle-ci doit faire partie de cette classe @ staticmethod est pratique dans ce cas.

@ classmethod Il est important lorsque vous voulez écrire une méthode factory et que cet attribut personnalisé peut être attaché à une classe. Cet attribut peut être remplacé dans la classe héritée.

Une comparaison entre ces deux méthodes peut être comme ci-dessous

Table

33
SIslam

En bref, @classmethod transforme une méthode normale en une méthode usine.

Explorons-le avec un exemple:

class PythonBook:
    def __init__(self, name, author):
        self.name = name
        self.author = author
    def __repr__(self):
        return f'Book: {self.name}, Author: {self.author}'

Sans une méthode @ class, vous devriez vous efforcer de créer des instances une par une et elles sont dispersées.

book1 = PythonBook('Learning Python', 'Mark Lutz')
In [20]: book1
Out[20]: Book: Learning Python, Author: Mark Lutz
book2 = PythonBook('Python Think', 'Allen B Dowey')
In [22]: book2
Out[22]: Book: Python Think, Author: Allen B Dowey

Comme par exemple avec @classmethod

class PythonBook:
    def __init__(self, name, author):
        self.name = name
        self.author = author
    def __repr__(self):
        return f'Book: {self.name}, Author: {self.author}'
    @classmethod
    def book1(cls):
        return cls('Learning Python', 'Mark Lutz')
    @classmethod
    def book2(cls):
        return cls('Python Think', 'Allen B Dowey')

Essaye-le:

In [31]: PythonBook.book1()
Out[31]: Book: Learning Python, Author: Mark Lutz
In [32]: PythonBook.book2()
Out[32]: Book: Python Think, Author: Allen B Dowey

Voir? Les instances sont créées avec succès dans une définition de classe et sont rassemblées.

En conclusion, @classmethod decorator convertit une méthode conventionnelle en une méthode d'usine. Utiliser classmethods permet d'ajouter autant de constructeurs alternatifs que nécessaire.

2
Algebra

@ classmethod

@classmethod peut être comparé à __init__. Vous pourriez penser que c'est une autre __init__(). C’est ainsi que python réalise la surcharge du constructeur de classe en c ++.

class C:
    def __init__(self, parameters):
        ....

    @classmethod
    def construct_from_func(cls, parameters):
        ....

obj1 = C(parameters)
obj2 = C.construct_from_func(parameters)

remarquez qu'ils ont tous deux une référence pour class comme premier argument de définition alors que __init__ utilise self mais construct_from_func utilise cls de manière conventionnelle.

@ staticmethod

@staticmethod peut être comparé à object method

class C:
    def __init__(self):
        ....

    @staticmethod
    def static_method(args):
        ....

    def normal_method(parameters):
        ....

result = C.static_method(parameters)
result = obj.normal_method(parameters)
1
Ke Zhang

Je suis débutant sur ce site, j'ai lu toutes les réponses ci-dessus et j'ai obtenu ce que je voulais. Cependant, je n'ai pas le droit de faire un vote par appel nominal. Je veux donc commencer à StackOverflow avec la réponse telle que je la comprends.

  • @staticmethod n'a pas besoin de self ou de cls en tant que premier paramètre de la méthode
  • @staticmethod et @classmethod fonction encapsulée peut être appelée par instance ou variable de classe
  • La fonction décorée @staticmethod a un impact sur une sorte de 'propriété immuable' que l'héritage de la sous-classe ne peut pas écraser sa fonction de classe de base qui est encapsulée par un décorateur @staticmethod.
  • @classmethod besoin de cls (nom de classe, vous pouvez changer le nom de la variable si vous le souhaitez, mais ce n'est pas conseillé) comme premier paramètre de la fonction
  • @classmethod toujours utilisé de manière sous-classe, l'héritage de sous-classe peut changer l'effet de la fonction de classe de base, c'est-à-dire @classmethod la fonction de classe de base encapsulée pourrait être écrasée par différentes sous-classes.
1
AYI

Une façon de penser légèrement différente qui pourrait être utile à quelqu'un ... Une méthode de classe est utilisée dans une super-classe pour définir le comportement de cette méthode lorsqu'elle est appelée par différentes classes enfant. Une méthode statique est utilisée lorsque nous voulons renvoyer la même chose quelle que soit la classe enfant que nous appelons.

0
boson