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)
où
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
owner
est 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 foo
gé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.