Nom d’un crotal, pourquoi “self” en Python ?

Python utilise « self » et non « this » pour designer la référence à l’objet courant, et de plus il vous oblige à le déclarer en paramètre des méthodes ! Lisez la suite si vous savez utiliser « self » mais que vous voudriez comprendre pourquoi ça marche.

« self » est une variable ordinaire

« self » n’est pas un mot réservé en Python. C’est une variable ordinaire. Elle n’a rien de spéciale. Rien. De fait elle n’existe pas par défaut, et si vous essayez de l’afficher sans l’avoir déclaré vous aurez une erreur:

>>> print(self)
 
NameError: name 'self' is not defined
 
>>> print(toto)
 
NameError: name 'toto' is not defined

Vous noterez que l’erreur n’est pas « Used ’self’ outside of a class » ou autre. C’est une banale erreur de déclaration. Et vous pouvez très bien mettre n’importe quoi dans « self »:

>>> self = "e-vidence.net"
>>> print(self)
e-vidence.net

En fait, l’usage de « self » est purement une convention. En Python vous écrivez :

>>> class Ornithorynque(object):
...
...    eggs = 3
...
...    def pondre(self):
...        return self.eggs>>> o = Ornithorynque()
>>> o.pondre()
3

Mais ce n’est pas du tout une obligation. Vous pouvez utiliser n’importe quel nom de variable en lieu et place de « self ». Vous préférez « this » :

>>> class Ornithorynque(object):
...
...    eggs = 3
...
...    def pondre(this):
...        return this.eggs>>> o = Ornithorynque()
>>> o.pondre()
3

Ça marche très bien. Vous vous sentez d’humeur taquine, voir carrément mégalo?

>>> class Ornithorynque(object):
...
...    eggs = 3
...
...    def pondre(MOOOOI):
...        return MOOOOI.eggs>>> o = Ornithorynque()
>>> o.pondre()
3

Ça marche également. La raison à cela est très simple : Python se fiche complètement de « self ». « self » n’est pas du tout le centre du problème, Python ne fonctionne pas comme les autres langages, en attribuant à une variable locale la référence à l’instance courante.

Pourtant, tout le monde utilise « self », et ce par pure convention. En effet, si tous les codeurs utilisent la même notation, on sait de quoi on parle. Ainsi, n’utilisez jamais autre chose que « self », tout ce que vous gagneriez serait un surplus de complexité. La plupart des outils de coloration syntaxique pour Python supposent que « self » est l’instance en cours.

Si c’est un choix collectif, pourquoi avoir choisi « self » et non « this », qui semble plus commun ? La raison est simple, Python s’inspire du langage Smalltalk-80, qui utilise aussi « self ». C++ est sorti la même année, en 1980, et utilisait « this ». Il n’y avait donc pas de choix « le plus populaire » à l’époque et on utilisait les conventions de son modèle. Ainsi, Java et PHP s’inspirant du C++, utilisent « this ».

Il y a une grosse différence cependant : Java et PHP attribuent automatiquement cette valeur à « this ». Pyhon se fiche complètement de « self », qui n’est qu’une conséquence d’un mécanisme plus important : le passage automatique d’une référence à l’instance en cours à toutes les méthodes de cette instance.

Comment « self » arrive dans votre méthode

« Self » est une variable ordinaire, et la raison pour laquelle on ne peut y accéder que dans une méthode et donc la même que pour toute variable : vous ne la déclarez que dans les méthodes, et donc son scope est limité au bloc dans lequel vous la déclarez.

Vous seul déclarez « self ». Vous êtes la seule raison pour laquelle « self » est là. Et vous pouvez très bien ne pas déclarer self :

>>> class Ornithorynque(object):
...
...    eggs = 3
...
...    def pondre(*selfless):
...        return selfless[0].eggs
...
>>> o = Ornithorynque()
>>> o.pondre()
3

Ce que vous ne pouvez pas faire en revanche, c’est déclarer une méthode sans aucun paramètre :

>>> class Ornithorynque(object):
...
...    eggs = 3
...
...    def pondre():
...        return 3
...
>>> o = Ornithorynque()
>>> o.pondre()
 
TypeError: pondre() takes no arguments (1 given)

Vous notez que l’erreur n’est pas « self is missing » mais quelque chose de plus simple, plus générique. Vous avez déclaré une méthode et cela marche parfaitement. C’est à l’utilisation que ça plante, on vous dit que votre méthode n’attend aucun argument, mais que pourtant on lui en a passé un. C’est une erreur très courante, qui n’a rien à voir avec la programmation orientée objet :

>>> def dis_bonjour():
...     print("bonjour")
...     
>>> dis_bonjour()
bonjour
>>> dis_bonjour("argument_bidon")
 
TypeError: dis_bonjour() takes no arguments (1 given)

Nous touchons ici le coeur du problème : Python ne créé pas automatiquement une variable qui contient une référence à l’instance en cours, il passe automatiquement l’instance en cours comme premier paramètre de toutes les méthodes de cette instance. A votre charge de récupérer cette référence dans la variable de votre choix.

C’est pour cela que l’on doit mettre « self » comme premier paramètre des toutes les méthodes : pour récupérer la référence que Python passe automatiquement. « Self » n’a rien de spécial, c’est un paramètre normal que vous créez, ce qui est spécial c’est que Python passe automatiquement au moins un paramètre à toutes les méthodes, même si on ne lui demande rien.

C’est une décision architecturale, une des philosophies de Python est « explicit is better than implicit », c’est à dire qu’il vaut mieux dire clairement ce qu’on veut plutôt que compter sur un sous-entendu. Quand vous êtes en Java, vous pouvez ne pas mettre « this », et dans la plupart des cas cela marchera car il est sous-entendu. En Python on ne peut pas : on doit dire ceci est l’instance, je la prend là, je la met là, et là je change son attribut.

Vous allez me dire, « ok, mais on aurait pu rendre explicite cette manipulation sans obliger à déclarer la variable ». C’est vrai après tout, cela aurait été plus automatique, et Python n’est-il pas là pour vous aider à économiser du temps en automatisant ce qui peut l’être ? C’est un point valide, et je ne suis pas Guido pour trouver une réponse pertinente, mais je peux vous présenter les côtés intéressants de cette fonctionnalité. Car s’en est une.

Jouons un peu avec et sans « self »

Python passe automatiquement l’instance de l’objet en cours si vous utilisez cette méthode depuis un objet.

>>> class Ornithorynque(object):
...
...    eggs = 3
...
...    def pondre(self):
...        return self.eggs
...
>>> o = Ornithorynque()
>>> o.pondre() # 'o' est un objet de type  Ornithorynque, python passe donc 'self' à 'pondre'

Mais on peut aussi passer cette instance manuellement ! Il suffit d’appeler la méthode à partir de la classe, et non d’un objet :

>>> orni = Ornithorynque()
>>> orni.eggs = 2
>>> Ornithorynque.pondre(orni) # Python ne passe pas de référence car on appel « pondre » depuis la classe
2

Ici on créé notre propre référence, et on la passe. C’est cette référence qui sera utilisée par la méthode « pondre », puisque c’est le premier paramètre, et que le premier paramètre est « self ».

Python est un langage qui vous laissent aller très loin dans l’introspection. Cette fonctionnalité en fait partie, et on peut lui trouver des usages très tordus. Imaginez par exemple qu’on peut récupérer des instances par paramétrages dynamique, et inclure ou non l’instance en cours :

>>> class Ornithorynque(object):
...
...    eggs = 3
...
...    def pondre(*args, **kwargs):
...        return tuple(orni.eggs for orni in args)
...
>>> o = Ornithorynque()
>>> o.eggs = 23
>>> o.pondre(Ornithorynque(), Ornithorynque(), Ornithorynque()) # l'instance en cours est dans le lot
(23, 3, 3, 3)
>>> Ornithorynque.pondre(Ornithorynque(), Ornithorynque(), Ornithorynque())
(3, 3, 3)

Une application concrète ? Ok, je n’en ai aucune, mais c’est super marrant non ?

Python est un langage très souple, il vous encourage largement au duck typing et rien ne vous empêche de prendre des morceaux de code qui n’ont rien à voir et d’en faire un tout cohérent :

>>> def voler(animal) :
…    return '%s voooooooole' % animal
...

Cette fonction, merci à Python, peut accepter des paramètres très variés :

>>> print(voler(Ornithorynque()))
<__main__.Ornithorynque object at 0xb74cc7ac> voooooooole
>>> print(voler('Je'))
Je voooooooole
>>> class Ornithorynque(object):
...
...    eggs = 3
...
...    def pondre(*args, **kwargs):
...        return tuple(orni.eggs for orni in args)
...
…   def __str__(self):
…       return 'Orni'>>> print(voler(Ornithorynque()))
Orni voooooooole

On peut très bien vous avoir donné cette fonction, par exemple dans une bibliothèque généraliste sur la lévitation qui ne connait rien aux ornithorynques. Tout développeur agile avec un peu d’expérience vous dira que c’est très crédible comme scenario ;-)

Maintenant, vous voulez écrire une méthode similaire pour votre cher mammifère ovipare. C’est inutile, hop, un petit cout de colle et vous rajoutez à ce bec de canard et cette queue de castor des ailes de cygne :

>>> Ornithorynque.voler = voler
>>> o = Ornithorynque()
>>> o.voler()
'Orni voooooooole'

La fonction « voler » accepte un argument qui doit être une instance de quelque chose. Si vous attachez cette fonction à la classe ‘Ornithorynque’, appeler « o.voler » implique que Python passe automatiquement l’instance en cours à cette fonction comme premier paramètre. Donc ça marche tout seul.

Notez que contrairement à Javascript, ça ne marche pas sur les built-ins :

>>> int.voler = voler
 
TypeError: can't set attributes of built-in/extension type 'int'

Ce qui est bien dommage car j’entrevois des applications titanesques dans le transport aérien si les ‘integers’ pouvaient voler à côté des ‘platipus’. Mais passons.

Sachez que Python est un langage qui s’étale en profondeur plus qu’en largeur. A chaque fois, c’est simple au début, mais si vous creusez vous verrez qu’il y a de nombreuses possibilités dont vous n’aviez même pas idée.

Aviez vous déjà pensé que tout est objet. Donc les fonctions sont des objets. Les classes sont des objets. Et qu’il peuvent être passés à la place de l’instance en cours…

Ça ouvre des perspectives non ?

D’ailleurs Python lui-même utilise déjà des petites astuces de ce genre. Par exemple il n’existe pas de méthodes statiques. Mais si vous voulez quelque chose similaire, vous pouvez utiliser le décorateur « classmethod ». En fait, cette méthode statique n’est qu’une méthode normale à laquelle on passe une référence à la classe en cours plutôt qu’une instance de l’objet en cours.

>>> class Ornithorynque(object):
...
...    eggs = 3
…
…   def __init__():
…      self.eggs = 4
…
…   @classmethod
...    def pondre(cls): # la convention ici passe de 'self' à 'cls' pour éviter la confusion
...        return cls.eggs
...
>>> print(Ornithorynque.pondre())
3

Malheureusement, même Python pose des limites et on ne peut pas définir de « self » par default. « self » n’est pas en cause, c’est une variable ordinaire. Mais Python surveille le premier paramètre d’une méthode pour voir si il correspond au même type que l’objet qu’il doit traiter :

>>> class SuperFile(file):
…     
…     def __init__(*args, **kwargs): # un peut de magie ici pour hériter de filefile.__init__(*args, **kwargs)
...
…     def super_write(self=sys.stdout, txt=''): # un 'self' par default qui écrit sur stdoutself.write(txt.upper())>>> sf = SuperFile("woooot.txt", "w")
>>> SuperFile.super_write(txt="J'aime les pythons et les ornitorynques")
 
TypeError: unbound method super_write() must be called with SuperFile instance as first argument (got nothing instead)

Dommage… Mais peut être que je suis tordu ?

Les commentaires sont fermés Contacter l'auteur.

Ça se comprend tout seul