Ce cours est visible gratuitement en ligne.

Paperback available in this course

Ce cours existe en eBook.

Certificate of achievement available at the end this course

Got it!
Apprenez à programmer en Python
Last updated on Monday, September 8, 2014
  • 4 semaines
  • Facile

Les propriétés

Au chapitre précédent, nous avons appris à créer nos premiers attributs et méthodes. Mais nous avons encore assez peu parlé de la philosophie objet. Il existe quelques confusions que je vais tâcher de lever.

Nous allons découvrir dans ce chapitre les propriétés, un concept propre à Python et à quelques autres langages, comme le Ruby. C'est une fonctionnalité qui, à elle seule, change l'approche objet et le principe d'encapsulation.

Qu'est-ce que l'encapsulation ?

L'encapsulation est un principe qui consiste à cacher ou protéger certaines données de notre objet. Dans la plupart des langages orientés objet, tels que le C++, le Java ou le PHP, on va considérer que nos attributs d'objets ne doivent pas être accessibles depuis l'extérieur de la classe. Autrement dit, vous n'avez pas le droit de faire, depuis l'extérieur de la classe, mon_objet.mon_attribut.

Mais c'est stupide ! Comment fait-on pour accéder aux attributs ?

On va définir des méthodes un peu particulières, appelées des accesseurs et mutateurs. Les accesseurs donnent accès à l'attribut. Les mutateurs permettent de le modifier. Concrètement, au lieu d'écrire mon_objet.mon_attribut, vous allez écrire mon_objet.get_mon_attribut(). De la même manière, pour modifier l'attribut écrivez mon_objet.set_mon_attribut(valeur) et non pas mon_objet.mon_attribut = valeur.

C'est bien tordu tout cela ! Pourquoi ne peut-on pas accéder aux attributs directement, comme on l'a fait au chapitre précédent ?

Ah mais d'abord, je n'ai pas dit que vous ne pouviez pas. Vous pouvez très bien accéder aux attributs d'un objet directement, comme on l'a fait au chapitre précédent. Je ne fais ici que résumer le principe d'encapsulation tel qu'on peut le trouver dans d'autres langages. En Python, c'est un peu plus subtil.

Mais pour répondre à la question, il peut être très pratique de sécuriser certaines données de notre objet, par exemple faire en sorte qu'un attribut de notre objet ne soit pas modifiable, ou alors mettre à jour un attribut dès qu'un autre attribut est modifié. Les cas sont multiples et c'est très utile de pouvoir contrôler l'accès en lecture ou en écriture sur certains attributs de notre objet.

L'inconvénient de devoir écrire des accesseurs et mutateurs, comme vous l'aurez sans doute compris, c'est qu'il faut créer deux méthodes pour chaque attribut de notre classe. D'abord, c'est assez lourd. Ensuite, nos méthodes se ressemblent plutôt. Certains environnements de développement proposent, il est vrai, de créer ces accesseurs et mutateurs pour nous, automatiquement. Mais cela ne résout pas vraiment le problème, vous en conviendrez.

Python a une philosophie un peu différente : pour tous les objets dont on n'attend pas une action particulière, on va y accéder directement, comme nous l'avons fait au chapitre précédent. On peut y accéder et les modifier en écrivant simplement mon_objet.mon_attribut. Et pour certains, on va créer des propriétés.

Les propriétés à la casserole

Pour commencer, une petite précision : en C++ ou en Java par exemple, dans la définition de classe, on met en place des principes d'accès qui indiquent si l'attribut (ou le groupe d'attributs) est privé ou public. Pour schématiser, si l'attribut est public, on peut y accéder depuis l'extérieur de la classe et le modifier. S'il est privé, on ne peut pas. On doit passer par des accesseurs ou mutateurs.

En Python, il n'y a pas d'attribut privé. Tout est public. Cela signifie que si vous voulez modifier un attribut depuis l'extérieur de la classe, vous le pouvez. Pour faire respecter l'encapsulation propre au langage, on la fonde sur des conventions que nous allons découvrir un peu plus bas mais surtout sur le bon sens de l'utilisateur de notre classe (à savoir, si j'ai écrit que cet attribut est inaccessible depuis l'extérieur de la classe, je ne vais pas chercher à y accéder depuis l'extérieur de la classe).

Les propriétés sont un moyen transparent de manipuler des attributs d'objet. Elles permettent de dire à Python : « Quand un utilisateur souhaite modifier cet attribut, fais cela ». De cette façon, on peut rendre certains attributs tout à fait inaccessibles depuis l'extérieur de la classe, ou dire qu'un attribut ne sera visible qu'en lecture et non modifiable. Ou encore, on peut faire en sorte que, si on modifie un attribut, Python recalcule la valeur d'un autre attribut de l'objet.

Pour l'utilisateur, c'est absolument transparent : il croit avoir, dans tous les cas, un accès direct à l'attribut. C'est dans la définition de la classe que vous allez préciser que tel ou tel attribut doit être accessible ou modifiable grâce à certaines propriétés.

Mais ces propriétés, c'est quoi ?

Hum… eh bien je pense que pour le comprendre, il vaut mieux les voir en action. Les propriétés sont des objets un peu particuliers de Python. Elles prennent la place d'un attribut et agissent différemment en fonction du contexte dans lequel elles sont appelées. Si on les appelle pour modifier l'attribut, par exemple, elles vont rediriger vers une méthode que nous avons créée, qui gère le cas où « on souhaite modifier l'attribut ». Mais trêve de théorie.

Les propriétés en action

Une propriété ne se crée pas dans le constructeur mais dans le corps de la classe. J'ai dit qu'il s'agissait d'une classe, son nom est property. Elle attend quatre paramètres, tous optionnels :

  • la méthode donnant accès à l'attribut ;

  • la méthode modifiant l'attribut ;

  • la méthode appelée quand on souhaite supprimer l'attribut ;

  • la méthode appelée quand on demande de l'aide sur l'attribut.

En pratique, on utilise surtout les deux premiers paramètres : ceux définissant les méthodes d'accès et de modification, autrement dit nos accesseur et mutateur d'objet.

Mais j'imagine que ce n'est pas très clair dans votre esprit. Considérez le code suivant, je le détaillerai plus bas comme d'habitude :

class Personne:
    """Classe définissant une personne caractérisée par :
    - son nom ;
    - son prénom ;
    - son âge ;
    - son lieu de résidence"""

    
    def __init__(self, nom, prenom):
        """Constructeur de notre classe"""
        self.nom = nom
        self.prenom = prenom
        self.age = 33
        self._lieu_residence = "Paris" # Notez le souligné _ devant le nom
    def _get_lieu_residence(self):
    """Méthode qui sera appelée quand on souhaitera accéder en lecture
        à l'attribut 'lieu_residence'"""
        
        
        print("On accède à l'attribut lieu_residence !")
        return self._lieu_residence
    def _set_lieu_residence(self, nouvelle_residence):
        """Méthode appelée quand on souhaite modifier le lieu de résidence"""
        print("Attention, il semble que {} déménage à {}.".format( \
                self.prenom, nouvelle_residence))
        self._lieu_residence = nouvelle_residence
    # On va dire à Python que notre attribut lieu_residence pointe vers une
    # propriété
    lieu_residence = property(_get_lieu_residence, _set_lieu_residence)

Vous devriez (j'espère) reconnaître la syntaxe générale de la classe. En revanche, au niveau du lieu de résidence, les choses changent un peu :

  • Tout d'abord, dans le constructeur, on ne crée pas un attribut self.lieu_residence mais self._lieu_residence. Il n'y a qu'un petit caractère de différence, le signe souligné _ placé en tête du nom de l'attribut. Et pourtant, ce signe change beaucoup de choses. La convention veut qu'on n'accède pas, depuis l'extérieur de la classe, à un attribut commençant par un souligné _. C'est une convention, rien ne vous l'interdit… sauf, encore une fois, le bon sens.

  • On définit une première méthode, commençant elle aussi par un souligné _, nommée _get_lieu_residence. C'est la même règle que pour les attributs : on n'accède pas, depuis l'extérieur de la classe, à une méthode commençant par un souligné _. Si vous avez compris ma petite explication sur les accesseurs et mutateurs, vous devriez comprendre rapidement à quoi sert cette méthode : elle se contente de renvoyer le lieu de résidence. Là encore, l'attribut manipulé n'est pas lieu_residence mais _lieu_residence. Comme on est dans la classe, on a le droit de le manipuler.

  • La seconde méthode a la forme d'un mutateur. Elle se nomme _set_lieu_residence et doit donc aussi être inaccessible depuis l'extérieur de la classe. À la différence de l'accesseur, elle prend un paramètre : le nouveau lieu de résidence. En effet, c'est une méthode qui doit être appelée quand on cherche à modifier le lieu de résidence, il lui faut donc le nouveau lieu de résidence qu'on souhaite voir affecté à l'objet.

  • Enfin, la dernière ligne de la classe est très intéressante. Il s'agit de la définition d'une propriété. On lui dit que l'attribut lieu_residence (cette fois, sans signe souligné _) doit être une propriété. On définit dans notre propriété, dans l'ordre, la méthode d'accès (l'accesseur) et celle de modification (le mutateur).

Quand on veut accéder à objet.lieu_residence, Python tombe sur une propriété redirigeant vers la méthode _get_lieu_residence. Quand on souhaite modifier la valeur de l'attribut, en écrivant objet.lieu_residence = valeur, Python appelle la méthode _set_lieu_residence en lui passant en paramètre la nouvelle valeur.

Ce n'est pas clair ? Voyez cet exemple :

>>> jean = Personne("Micado", "Jean")
>>> jean.nom
'Micado'
>>> jean.prenom
'Jean'
>>> jean.age
33
>>> jean.lieu_residence
On accède à l'attribut lieu_residence !
'Paris'
>>> jean.lieu_residence = "Berlin"
Attention, il semble que Jean déménage à Berlin.
>>> jean.lieu_residence
On accède à l'attribut lieu_residence !
'Berlin'
>>>

Notre accesseur et notre mutateur se contentent d'afficher un message, pour bien qu'on se rende compte que ce sont eux qui sont appelés quand on souhaite manipuler l'attribut lieu_residence. Vous pouvez aussi ne définir qu'un accesseur, dans ce cas l'attribut ne pourra pas être modifié.

Il est aussi possible de définir, en troisième position du constructeur property, une méthode qui sera appelée quand on fera del objet.lieu_residence et, en quatrième position, une méthode qui sera appelée quand on fera help(objet.lieu_residence). Ces deux dernières fonctionnalités sont un peu moins utilisées mais elles existent.

Voilà, vous connaissez à présent la syntaxe pour créer des propriétés. Entraînez-vous, ce n'est pas toujours évident au début. C'est un concept très puissant, il serait dommage de passer à côté.

Résumons le principe d'encapsulation en Python

Dans cette section, je vais condenser un peu tout le chapitre. Nous avons vu qu'en Python, quand on souhaite accéder à un attribut d'un objet, on écrit tout bêtement objet.attribut. Par contre, on doit éviter d'accéder ainsi à des attributs ou des méthodes commençant par un signe souligné _, question de convention. Si par hasard une action particulière doit être menée quand on accède à un attribut, pour le lire tout simplement, pour le modifier, le supprimer…, on fait appel à des propriétés. Pour l'utilisateur de la classe, cela revient au même : il écrit toujours objet.attribut. Mais dans la définition de notre classe, nous faisons en sorte que l'attribut visé soit une propriété avec certaines méthodes, accesseur, mutateur ou autres, qui définissent ce que Python doit faire quand on souhaite lire, modifier, supprimer l'attribut.

Avec ce concept, on perd beaucoup moins de temps. On ne fait pas systématiquement un accesseur et un mutateur pour chaque attribut et le code est bien plus lisible. C'est autant de gagné.

Certaines classes ont besoin qu'un traitement récurrent soit effectué sur leurs attributs. Par exemple, quand je souhaite modifier un attribut de l'objet (n'importe quel attribut), l'objet doit être enregistré dans un fichier. Dans ce cas, on n'utilisera pas les propriétés, qui sont plus utiles pour des cas particuliers, mais plutôt des méthodes spéciales, que nous découvrirons au prochain chapitre.

En résumé

  • Les propriétés permettent de contrôler l'accès à certains attributs d'une instance.

  • Elles se définissent dans le corps de la classe en suivant cette syntaxe : nom_propriete = propriete(methode_accesseur, methode_mutateur, methode_suppression, methode_aide).

  • On y fait appel ensuite en écrivant objet.nom_propriete comme pour n'importe quel attribut.

  • Si l'on souhaite juste lire l'attribut, c'est la méthode définie comme accesseur qui est appelée.

  • Si l'on souhaite modifier l'attribut, c'est la méthode mutateur, si elle est définie, qui est appelée.

  • Chacun des paramètres à passer à property est optionnel.

Example of certificate of achievement
Example of certificate of achievement