Différences de méthode de classe en Python: lié, non lié et statique

242

Quelle est la différence entre les méthodes de classe suivantes?

Est-ce que l'un est statique et l'autre non?

class Test(object):
  def method_one(self):
    print "Called method_one"

  def method_two():
    print "Called method_two"

a_test = Test()
a_test.method_one()
a_test.method_two()
Franck Mesirard
la source
18
Aucune différence autre que la définition de method_two () n'est invalide et son appel échoue.
anatoly techtonik du
14
@techtonik: Rien ne cloche dans la définition de method_two! Il est appelé dans une spécification incorrecte / invalide, c'est-à-dire avec un argument supplémentaire.
0xc0de
1
Les vôtres sont les deux méthodes d'instance , pas les méthodes de classe. Vous créez une méthode de classe en l'appliquant @classmethodà la définition. Le premier paramètre doit être appelé à la clsplace de selfet recevra l'objet classe plutôt qu'une instance de votre classe: Test.method_three()et a_test.method_three()sont équivalents.
Lutz Prechelt
Pourquoi voudriez-vous créer une définition de fonction sans l' selfargument? Existe-t-il un cas d'utilisation solide pour cela?
alpha_989

Réponses:

412

En Python, il existe une distinction entre les méthodes liées et non liées .

Fondamentalement, un appel à une fonction membre (comme method_one), une fonction liée

a_test.method_one()

est traduit en

Test.method_one(a_test)

c'est-à-dire un appel à une méthode non liée. Pour cette raison, un appel à votre version de method_twoéchouera avec unTypeError

>>> a_test = Test() 
>>> a_test.method_two()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given) 

Vous pouvez modifier le comportement d'une méthode à l'aide d'un décorateur

class Test(object):
    def method_one(self):
        print "Called method_one"

    @staticmethod
    def method_two():
        print "Called method two"

Le décorateur indique à la métaclasse par défaut intégrée type(la classe d'une classe, cf. cette question ) de ne pas créer de méthodes liées pour method_two.

Maintenant, vous pouvez appeler une méthode statique à la fois sur une instance ou sur la classe directement:

>>> a_test = Test()
>>> a_test.method_one()
Called method_one
>>> a_test.method_two()
Called method_two
>>> Test.method_two()
Called method_two
Torsten Marek
la source
17
J'appuie cette réponse, elle est supérieure à la mienne. Bravo Torsten :)
freespace
24
en python 3, les méthodes non liées sont obsolètes. au lieu de cela, il n'y a qu'une fonction.
boldnik
@boldnik, pourquoi dites-vous que les méthodes non liées sont obsolètes? des méthodes statiques sont toujours présentes dans la documentation: docs.python.org/3/library/functions.html#staticmethod
alpha_989
195

Les méthodes en Python sont une chose très, très simple une fois que vous avez compris les bases du système de descripteurs. Imaginez la classe suivante:

class C(object):
    def foo(self):
        pass

Voyons maintenant cette classe dans le shell:

>>> C.foo
<unbound method C.foo>
>>> C.__dict__['foo']
<function foo at 0x17d05b0>

Comme vous pouvez le voir si vous accédez à l' fooattribut de la classe, vous obtenez une méthode non liée, mais à l'intérieur du stockage de classe (le dict), il y a une fonction. Pourquoi ça? La raison en est que la classe de votre classe implémente un __getattribute__qui résout les descripteurs. Cela semble complexe, mais ce n'est pas le cas. C.fooest à peu près équivalent à ce code dans ce cas particulier:

>>> C.__dict__['foo'].__get__(None, C)
<unbound method C.foo>

C'est parce que les fonctions ont une __get__méthode qui en fait des descripteurs. Si vous avez une instance d'une classe, c'est presque la même chose, c'est juste Nonel'instance de classe:

>>> c = C()
>>> C.__dict__['foo'].__get__(c, C)
<bound method C.foo of <__main__.C object at 0x17bd4d0>>

Maintenant, pourquoi Python fait-il cela? Parce que l'objet méthode lie le premier paramètre d'une fonction à l'instance de la classe. C'est de là que vient le moi. Maintenant, parfois, vous ne voulez pas que votre classe fasse d'une fonction une méthode, c'est là staticmethodqu'entre en jeu:

 class C(object):
  @staticmethod
  def foo():
   pass

Le staticmethoddécorateur encapsule votre classe et implémente un mannequin __get__qui renvoie la fonction encapsulée en tant que fonction et non en tant que méthode:

>>> C.__dict__['foo'].__get__(None, C)
<function foo at 0x17d0c30>

J'espère que cela l'explique.

Armin Ronacher
la source
12
Le staticmethoddécorateur encapsule votre classe (...) Cette phrase est un peu trompeuse car la classe qui est encapsulée est la classe de la méthode fooet non la classe dans laquelle elle fooest définie.
Piotr Dobrogost
12

Lorsque vous appelez un membre de classe, Python utilise automatiquement une référence à l'objet comme premier paramètre. La variable selfne signifie en fait rien, c'est juste une convention de codage. Vous pourriez l'appeler gargaloosi vous le vouliez. Cela dit, l'appel à déclencherait method_twoun TypeError, car Python essaie automatiquement de passer un paramètre (la référence à son objet parent) à une méthode définie comme n'ayant aucun paramètre.

Pour le faire fonctionner, vous pouvez ajouter ceci à votre définition de classe:

method_two = staticmethod(method_two)

ou vous pouvez utiliser le @staticmethod décorateur de fonction .

Justin Poliey
la source
4
Vous voulez dire "la syntaxe du décorateur de fonction @staticmethod".
tzot
11
>>> class Class(object):
...     def __init__(self):
...         self.i = 0
...     def instance_method(self):
...         self.i += 1
...         print self.i
...     c = 0
...     @classmethod
...     def class_method(cls):
...         cls.c += 1
...         print cls.c
...     @staticmethod
...     def static_method(s):
...         s += 1
...         print s
... 
>>> a = Class()
>>> a.class_method()
1
>>> Class.class_method()    # The class shares this value across instances
2
>>> a.instance_method()
1
>>> Class.instance_method() # The class cannot use an instance method
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method instance_method() must be called with Class instance as first argument (got nothing instead)
>>> Class.instance_method(a)
2
>>> b = 0
>>> a.static_method(b)
1
>>> a.static_method(a.c) # Static method does not have direct access to 
>>>                      # class or instance properties.
3
>>> Class.c        # a.c above was passed by value and not by reference.
2
>>> a.c
2
>>> a.c = 5        # The connection between the instance
>>> Class.c        # and its class is weak as seen here.
2
>>> Class.class_method()
3
>>> a.c
5
kzh
la source
2
Class.instance_method() # The class cannot use an instance methodil peut utiliser. Il suffit de passer l'instance manuellement:Class.instance_method(a)
warvariuc
@warwaruk C'est là, regardez la ligne en dessous de la TyeErrorligne.
kzh
oui je l'ai vu plus tard. encore, imo, il n'est pas correct de dire «La classe ne peut pas utiliser une méthode d'instance», car vous venez de le faire une ligne ci-dessous.
warvariuc
@kzh, Merci pour votre explication. Lorsque vous avez appelé a.class_method(), il semble avoir a.cété mis à jour vers 1, donc l'appel a Class.class_method()mis à jour la Class.cvariable vers 2. Cependant, lors de votre affectation a.c=5, pourquoi ne Class.cpas être mis à jour 5?
alpha_989
@ alpha_989 python cherche d'abord un attribut directement sur une propre instance d'ovjects et s'il n'est pas là, il le recherche sur sa classe par défaut. Si vous avez d'autres questions à ce sujet, n'hésitez pas à ouvrir une question et à la lier ici et je serais heureux de vous aider davantage.
kzh
4

method_two ne fonctionnera pas car vous définissez une fonction membre mais ne lui dites pas de quoi la fonction est membre. Si vous exécutez la dernière ligne, vous obtiendrez:

>>> a_test.method_two()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given)

Si vous définissez des fonctions membres pour une classe, le premier argument doit toujours être «self».

Jon Cage
la source
3

Explication précise d'Armin Ronacher ci-dessus, développant ses réponses pour que les débutants comme moi le comprennent bien:

La différence dans les méthodes définies dans une classe, qu'il s'agisse d'une méthode statique ou d'une méthode d'instance (il existe encore un autre type - méthode de classe - non abordée ici, donc en la sautant), réside dans le fait qu'elles sont en quelque sorte liées à l'instance de classe ou non. Par exemple, dites si la méthode reçoit une référence à l'instance de classe pendant l'exécution

class C:
    a = [] 
    def foo(self):
        pass

C # this is the class object
C.a # is a list object (class property object)
C.foo # is a function object (class property object)
c = C() 
c # this is the class instance

La __dict__propriété dictionnaire de l'objet classe contient la référence à toutes les propriétés et méthodes d'un objet classe et donc

>>> C.__dict__['foo']
<function foo at 0x17d05b0>

la méthode foo est accessible comme ci-dessus. Un point important à noter ici est que tout en python est un objet et donc les références dans le dictionnaire ci-dessus pointent elles-mêmes vers d'autres objets. Permettez-moi de les appeler objets de propriété de classe - ou en tant que CPO dans le cadre de ma réponse par souci de concision.

Si un CPO est un descripteur, alors l'interpréteur python appelle la __get__()méthode du CPO pour accéder à la valeur qu'il contient.

Afin de déterminer si un CPO est un descripteur, l'interpréteur python vérifie s'il met en œuvre le protocole de descripteur. Mettre en œuvre le protocole descripteur, c'est mettre en œuvre 3 méthodes

def __get__(self, instance, owner)
def __set__(self, instance, value)
def __delete__(self, instance)

par exemple

>>> C.__dict__['foo'].__get__(c, C)

  • self est le CPO (il peut s'agir d'une instance de liste, str, fonction, etc.) et est fourni par le runtime
  • instance est l'instance de la classe où ce CPO est défini (l'objet 'c' ci-dessus) et doit être explicitement fourni par nous
  • ownerest la classe où ce CPO est défini (l'objet de classe 'C' ci-dessus) et doit être fourni par nous. Cependant, c'est parce que nous l'appelons sur le CPO. lorsque nous l'appelons sur l'instance, nous n'avons pas besoin de fournir cela car le runtime peut fournir l'instance ou sa classe (polymorphisme)
  • value est la valeur prévue pour le CPO et doit être fournie par nous

Tous les CPO ne sont pas des descripteurs. Par exemple

>>> C.__dict__['foo'].__get__(None, C)
<function C.foo at 0x10a72f510> 
>>> C.__dict__['a'].__get__(None, C)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute '__get__'

En effet, la classe de liste n'implémente pas le protocole de descripteur.

Ainsi, l'argument self in c.foo(self)est requis car sa signature de méthode est en fait la suivante C.__dict__['foo'].__get__(c, C)(comme expliqué ci-dessus, C n'est pas nécessaire car il peut être découvert ou polymorphe) Et c'est aussi pourquoi vous obtenez une TypeError si vous ne passez pas cet argument d'instance requis.

Si vous remarquez que la méthode est toujours référencée via la classe Object C et que la liaison avec l'instance de classe est réalisée en passant un contexte sous la forme de l'objet d'instance dans cette fonction.

C'est assez génial car si vous choisissez de ne conserver aucun contexte ou aucune liaison à l'instance, tout ce qui était nécessaire était d'écrire une classe pour encapsuler le descripteur CPO et remplacer sa __get__()méthode pour ne nécessiter aucun contexte. Cette nouvelle classe est ce que nous appelons un décorateur et est appliquée via le mot clé@staticmethod

class C(object):
  @staticmethod
  def foo():
   pass

L'absence de contexte dans le nouveau CPO enveloppé ne foogénère pas d'erreur et peut être vérifiée comme suit:

>>> C.__dict__['foo'].__get__(None, C)
<function foo at 0x17d0c30>

Le cas d'utilisation d'une méthode statique est plus un espace de noms et une maintenabilité du code (le retirer d'une classe et le rendre disponible dans tout le module, etc.).

Il est peut-être préférable d'écrire des méthodes statiques plutôt que des méthodes d'instance dans la mesure du possible, sauf si vous devez bien sûr contextualiser les méthodes (comme les variables d'instance d'accès, les variables de classe, etc.). L'une des raisons est de faciliter la récupération de place en ne conservant pas de référence indésirable aux objets.

supi
la source
1

c'est une erreur.

tout d'abord, la première ligne doit être comme ça (attention aux majuscules)

class Test(object):

Chaque fois que vous appelez une méthode d'une classe, elle se prend comme premier argument (d'où le nom self) et method_two donne cette erreur

>>> a.method_two()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given)
hayalci
la source
1

Le second ne fonctionnera pas parce que lorsque vous l'appelez comme ça, python essaie de l'appeler en interne avec l'instance a_test comme premier argument, mais votre method_two n'accepte aucun argument, donc ça ne marchera pas, vous obtiendrez un runtime Erreur. Si vous voulez l'équivalent d'une méthode statique, vous pouvez utiliser une méthode de classe. Les méthodes de classe en Python sont beaucoup moins nécessaires que les méthodes statiques dans des langages comme Java ou C #. Le plus souvent, la meilleure solution consiste à utiliser une méthode dans le module, en dehors d'une définition de classe, celles-ci fonctionnent plus efficacement que les méthodes de classe.

Vasil
la source
Si je définis une fonction Class Test(object): @staticmethod def method_two(): print(“called method_two”) Un cas d'utilisation, je pensais à quand vous voulez que la fonction fasse partie d'une classe, mais ne voulez pas que l'utilisateur accède directement à la fonction. Ainsi, le method_twopeut être appelé par d'autres fonctions à l'intérieur de l' Testinstance, mais ne peut pas être appelé en utilisant a_test.method_two(). Si j'utilise def method_two(), cela fonctionnera-t-il pour ce cas d'utilisation? Ou existe-t-il un meilleur moyen de modifier la définition de la fonction, afin qu'elle fonctionne comme prévu dans le cas d'utilisation ci-dessus?
alpha_989
1

L'appel à method_two lèvera une exception pour ne pas accepter le paramètre auto, le runtime Python le transmettra automatiquement.

Si vous souhaitez créer une méthode statique dans une classe Python, décorez-la avec le staticmethod decorator .

Class Test(Object):
  @staticmethod
  def method_two():
    print "Called method_two"

Test.method_two()
MvdD
la source
1

Veuillez lire cette documentation de Guido First Class tout expliquant clairement comment les méthodes Unbound, Bound sont nées.

James Sapam
la source
0

La définition de method_twon'est pas valide. Lorsque vous appelez method_two, vous obtiendrezTypeError: method_two() takes 0 positional arguments but 1 was given de l'interprète.

Une méthode d'instance est une fonction bornée lorsque vous l'appelez comme a_test.method_two(). Il accepte automatiquement self, qui pointe vers une instance de Test, comme premier paramètre. Grâce au selfparamètre, une méthode d'instance peut accéder librement aux attributs et les modifier sur le même objet.

Yossarian42
la source
0

Méthodes non liées

Les méthodes non liées sont des méthodes qui ne sont pas encore liées à une instance de classe particulière.

Méthodes liées

Les méthodes liées sont celles qui sont liées à une instance spécifique d'une classe.

Comme indiqué ici , self peut faire référence à différentes choses selon que la fonction est liée, non liée ou statique.

Jetez un œil à l'exemple suivant:

class MyClass:    
    def some_method(self):
        return self  # For the sake of the example

>>> MyClass().some_method()
<__main__.MyClass object at 0x10e8e43a0># This can also be written as:>>> obj = MyClass()

>>> obj.some_method()
<__main__.MyClass object at 0x10ea12bb0>

# Bound method call:
>>> obj.some_method(10)
TypeError: some_method() takes 1 positional argument but 2 were given

# WHY IT DIDN'T WORK?
# obj.some_method(10) bound call translated as
# MyClass.some_method(obj, 10) unbound method and it takes 2 
# arguments now instead of 1 

# ----- USING THE UNBOUND METHOD ------
>>> MyClass.some_method(10)
10

Puisque nous n'avons pas utilisé l'instance de classe - obj - lors du dernier appel, nous pouvons en quelque sorte dire qu'elle ressemble à une méthode statique.

Si oui, quelle est la différence entre un MyClass.some_method(10)appel et un appel à une fonction statique décorée avec un @staticmethoddécorateur?

En utilisant le décorateur, nous indiquons clairement que la méthode sera utilisée sans créer d'abord une instance pour elle. Normalement, on ne s'attendrait pas à ce que les méthodes des membres de classe soient utilisées sans l'instance et y accéder peut provoquer des erreurs possibles selon la structure de la méthode.

De plus, en ajoutant le @staticmethoddécorateur, nous permettons également d'être atteint à travers un objet.

class MyClass:    
    def some_method(self):
        return self    

    @staticmethod
    def some_static_method(number):
        return number

>>> MyClass.some_static_method(10)   # without an instance
10
>>> MyClass().some_static_method(10)   # Calling through an instance
10

Vous ne pouvez pas faire l'exemple ci-dessus avec les méthodes d'instance. Vous pouvez survivre au premier (comme nous l'avons fait auparavant), mais le second sera traduit en un appel non lié MyClass.some_method(obj, 10)qui déclenchera un TypeErrorpuisque la méthode d'instance prend un argument et que vous avez involontairement essayé d'en passer deux.

Ensuite, vous pourriez dire: «si je peux appeler des méthodes statiques via une instance et une classe, MyClass.some_static_methodet MyClass().some_static_methodque ce soient les mêmes méthodes». Oui!

alexa
la source
0

Méthode liée = méthode d'instance

Méthode non liée = méthode statique.

Débutant Python
la source
Veuillez ajouter quelques explications supplémentaires à votre réponse afin que d'autres puissent en tirer des enseignements. Par exemple, jetez un œil aux autres réponses à cette question
Nico Haase