Bienvenue sur IndexError.

Ici vous pouvez poser des questions sur Python et le Framework Django.

Mais aussi sur les technos front comme React, Angular, Typescript et Javascript en général.

Consultez la FAQ pour améliorer vos chances d'avoir des réponses à vos questions.

Une fonction appelée depuis une classe appelle-t'elle toujours le paramètre self ?

+5 votes

Dans l'ex41 du célèbre Learn Python The Hard Way (Python 2.7) on fabrique (physiquement) et étudie des flashcards du type question/réponse afin de mémoriser par coeur des concepts concernant la POO (classes, instances, etc).

Voici celle qui me pose problème :

Front of the card (question) :

foo.M(J) 

Back of the card (réponse) :

"From foo get the M function, and call it with parameters self, J."

Si je comprends bien :

  1. on prend la fonction M depuis l'instance "foo", de la même manière
    qu'on appellerait une fonction depuis un module
  2. et on l'appelle ensuite avec l'argument "J"

Alors d'où il sort ce "self" ?

EDIT : ma question était très mal posée, la voici de nouveau :
Toute fonction au sein d'une classe prends toujours self en premier paramètre, autrement dit elle se réfère à son conteneur (l'instance de classe).
Est-ce exact ? (qu'il faille toujours mettre self en premier)
Pourquoi faut-il le faire ?
Je trouve que ça fout la merde car on est obligé d'appeler les fonctions de classe avec N-1 arguments, N étant self + arguments à passer...

EDIT 2 : Je vais potasser Le Guide Ultime sur la POO. Merci à tous pour vos réponses !

demandé 19-Jan-2015 par Hoglob (258 points)
edité 20-Jan-2015 par Hoglob

Merci pour la réponse acceptée. Je t'ai fais une update, j'espère que c'est mieux. Mais c'est vrai que pour expliquer la POO, il faut partir de plus loin encore.

6 Réponses

+11 votes
 
Meilleure réponse

Ok, j'édite, et on oublie la variable de classe, car elle porte à confusion.

Quand tu fais :

class Personne:
    def __init__(moi):
        moi.nom = "Anonyme"
    def presente(moi):
        print('Bonjour, je suis %s' % moi.nom)

Tu créés un classe Personne avec des méthodes presente et __init__ qui acceptent un premier paramètre qu'on choisit d'appeler moi.

Tu as écris une classe. C'est un plan. Le plan ne fait rien. Pour te faciliter la vie, imagine toi que le plan n'est pas manipulable, que tu ne peux pas appeler de méthodes à partir de ce plan (je simplifie, tu apprendras plus tard qu'il y a bien plus derrière tout ça).

Pour utiliser le code de ta classe, il faut créer un objet, c'est à dire quelque chose de concret à partir de ce plan. L'objet va ressembler à ce que tu as décris avec ta classe Personne.

Si tu fais :

inconnu = Personne()

inconnu va contenir ce qu'on appelle une instance de Personne, c'est à dire un objet créé à partir de la classe (ton plan) Personne. Cette création (cette "instanciation" en fait), déclenche automatiquement l'appel de la méthode __init__ qui va recevoir inconnu en premier paramètre. Les méthode __nom__ sont des méthodes ordinaires en Python, mais elles sont appelées automatiquement à un moment donné. Pour __init__, c'est à la création d'un objet.

Tu ne le vois pas, c'est invisible, mais quand tu fais :

inconnu = Personne()

Python fait juste derrière :

inconnu.__init__(inconnu)

Ce qui fait que le code suivant est exécuté :

    def __init__(moi):
        moi.nom = "Anonyme"

Avec moi ayant la même valeur que inconnu, à savoir, l'objet en cours.

Du coup, dès la création d'un objet de type Personne, on attache un attribut, nommé "nom", à cet objet, qui contient une chaine de caractères :

>>> inconnu = Personne()
>>> print(inconnu.nom)
Anonyme

Cette notion d'objet en cours est important. En effet, le seul lien entre tes méthodes, c'est cet objet en cours. Cet objet en cours qui contient tes données, dans notre cas, le nom.

La POO, ça sert à ça : grouper les données (l'instance et ses attributs) avec le traitement de ces données (les méthodes).

Donc derrière, si tu fais ça :

>>> inconnu = Personne()
>>> inconnu.presente()
Bonjour, je suis Anonyme"

presente() va manipuler le même objet que __init__ à savoir l'objet en cours. Car Python derrière fait vraiment :

>>> inconnu = Personne()
>>> inconnu.__init__(inconnu)
>>> inconnu.presente(inconnu)

On comprends mieux la notion d'objet en cours si on créé plusieurs objets :

>>> inconnu = Personne()
>>> robert = Personne()
>>> robert.nom = "Robert"

Maintenant, j'ai 2 objets. J'ai toujours un seul plan : Personne. Mais j'ai deux objets créés à partir de ce plan : celui dans la variable inconnu et celui dans la variable robert.

Ces deux objets sont complètement différents :

>>> print(robert == inconnu)
False
>>> print(robert.nom)
Robert
>>> print(inconnu.nom)
Anonyme

Et donc, quand on appelle une méthode sur chaque objet, le résultat est différent :

>>> inconnu.presente() # python fait inconnu.presente(inconnu)
Bonjour, je suis Anonyme
>>> robert.presente() # python fait robert.presente(robert)
Bonjour, je suis Robert

Ce mécanisme te permet de savoir sur quel objet tu travailles depuis l’intérieur de la méthode. Ainsi, dans :

    def presente(moi):
        print('Bonjour, je suis %s' % moi.nom)

Tu es certain que moi est toujours l'objet en cours de manipulation.

Tout le monde s'est néanmoins mis d'accord sur comment appeler ce premier paramètre : self. Car cela veut dire "soi" en anglais.

On écrirait donc cette classe plutôt ainsi :

class Personne:
    def __init__(self, nom="Anonyme"):
        self.nom = nom
    def presente(self):
        print('Bonjour, je suis %s' % self.nom)

Il existe quelques exceptions à cette règle, notamment les méthodes de classes et les méthodes statiques :

class Personne:
    def __init__(self, nom="Anonyme")
        self.nom = nom

    @classmethod
    def hello(cls):
        print('Bonjour')

    @staticmethod
    def autre_hello():
        print('Bonjour')

Mais tu n'as pas à t'en préoccuper pour le moment. Je le dis juste pour que quand tu tombes dessus, tu ne sois pas surpris.

répondu 19-Jan-2015 par Sam (4,984 points)
edité 24-Jan-2015 par Sam

j'attendais l'explication du maître :) c'est tjs délicieux à lire quand c'est "limpide" !

une typo : c'est @staticmethod le décorateur des méthodes de classe ;)

class Personne:
nom = "Anonyme"
def presente(moi):
    print('Bonjour, je suis %s' % moi.nom)

Rien qu'au début déjà je ne pige pas pourquoi on a:

nom = "Anonyme"

et non pas :

moi.nom = "Anonyme"

J'ai trouvé ça perturbant de devoir appeler

moi.nom # dans le print

Ensuite je lis :

C'est vrai pour toutes les méthodes : le premier paramètre est toujours l'objet en cours de manipulation.

C'est ce que je lis aussi dans l'article Pourquoi self en Python ?

La réponse semble être dans :

Python fait automatiquement, et de manière invisible, pour toi:

robert.presente(robert)

Mais ça coince encore dans mon cervelet... :(

PS : bravo pour ce "StackOverflow" en français, c'est super !
Je trouve néanmoins que la box textarea pour commenter freeze beaucoup quand j'écris dedans (sous Chrome). J'ai été obligé de rédiger cette réponse sous Notepad++ et copier/coller ensuite... :(

Ok, je vois ce que tu veux dire. Je vais essayer d'améliorer la réponse. En attendant, n'hésite pas à lire http://sametmax.com/le-guide-ultime-et-definitif-sur-la-programmation-orientee-objet-en-python-a-lusage-des-debutants-qui-sont-rassures-par-les-textes-detailles-qui-prennent-le-temps-de-tout-expliquer-partie-1/ car là on touche à la POO en général. Ce n'est pas juste self que tu ne comprends pas, c'est toute la POO.

Il me semble que quelques typos subsistent:

  • Manque ":" à la fin des méthodes init
  • L'attribut "nom" devient "name" pour deux exemples de codes (Là ou Robert intervient)

Merci pour la clarté des propos.

Merci, c'est corrigé :)

0 votes

"self" est juste une convention d'écriture. Dans une classe, chaque méthode (sauf si explicitement statique) prend en premier paramètre l'instance qui est à l'origine de l'appel de manière automatique. Dans le cas d'une méthode de classe, ce paramètre est conventionnellement appelé "cls" et il s'agira de la classe et plus de l'instance. C'est tout.

répondu 19-Jan-2015 par debnet (1,024 points)
0 votes

Le paramètre self est passé dans la définition de la méthode et non dans l'appel à celle ci et c'est effectivement pour pouvoir récupérer l'instance en cour. Dans certain langage elle est masqué mais il est toujours là.

répondu 19-Jan-2015 par ptank (352 points)
0 votes

C'est exact (et on peut faire ça avec n'importe quoi en python : tout est objet, donc faire X.Y fonctionne sur n'importe quel objet).

La subtilité, c'est que dans le cas d'une classe, quand on écrit par exemple

class Foo:
    def M(self, J):
        pass
foo = A()

les fonctions foo.M et Foo.M sont deux choses différentes : foo.M est équivalent à lambda x: Foo.M(foo, x). Le self dans l'histoire c'est une convention pour le premier argument d'une méthode, et il ce premier argument a comme valeur l'objet sur lequel est appelé la méthode.

(Au passage, le code ne respecte pas la PEP8)

répondu 19-Jan-2015 par Phynx (298 points)
edité 19-Jan-2015 par Phynx
+6 votes

En Python le self n'est qu'une simple convention qui veut dire qu'on se trouve dans une méthode d'une classe et que par définition le premier paramètre de la méthode sera l'instance qui appelle la méthode.

Un article de sametmax explique très bien le self.

Donc ici dans la déclaration de ta méthode M tu vas avoir un truc du genre :

class Foo:
    def M(self, J):
        # corps de méthode

Donc dans ton cas :

  1. Oui tu appelles la méthode M depuis l'instance foo de la classe Foo
  2. Oui tu l'appelles avec l'argument J, mais un argument en plus sera passé et par convention il s'appelle self
répondu 19-Jan-2015 par Hawke (466 points)
edité 19-Jan-2015 par Hawke

Je suis en train de lire toutes les réponses une par une, je me suis peut-être mal exprimé car c'est la première que je lis qui semble avoir compris à ma question.

mais un argument en plus sera passé et par convention il s'appelle self

C'est précisément ça que je pige pas.

Je vais lire l'article de Sam&Max que tu m'as partagé, merci.

+5 votes

Comme dit par tout le monde avant moi, self n'est rien d'autre qu'une convention. On pourrait l'appeler autrement, mais c'est plutôt déconseillé afin de garder de la clarté.

Le self dans une méthode est en fait l'instance sur laquelle l'appel de la méthode est effectué passée implicitement.

Pas clair?
Admettons que ta classe ressemble à ceci:

class Foo(object):
    def M(self, J):
        pass

Ceci:

foo = Foo()
foo.M(42)

Est équivalent à ceci:

foo = Foo()
Foo.M(foo, 42)
répondu 19-Jan-2015 par Arza (726 points)
edité 20-Jan-2015 par Arza

f.M(42)

Tu voulais peut-être écrire

foo.M(42) 

?

Je suis perdu.

C'est exact. Je me suis corrigé.

Merci pour l'edit, ce n'est toujours pas clair pour moi, j'ai aussi ré-édité ma question du coup.

...