6. POO I : Objets et méthodes#

6.1. Vue d’ensemble#

Le paradigme de programmation traditionnel (pensez à Fortran, C, MATLAB, etc.) est appelé procédural.

Il fonctionne de la manière suivante

  • Le programme possède un état qui correspond aux valeurs de ses variables.

  • Des fonctions sont appelées pour agir sur cet état et le transformer.

  • Les sorties finales sont produites par une séquence d’appels de fonctions.

Deux autres paradigmes importants sont la programmation orientée objet (POO) et la programmation fonctionnelle.

Dans le paradigme POO, les données et les fonctions sont regroupées ensemble dans des « objets » — et les fonctions, dans ce contexte, sont appelées méthodes.

Les méthodes sont appelées pour transformer les données contenues dans l’objet.

  • Pensez à une liste Python qui contient des données et possède des méthodes telles que append() et pop() qui transforment ces données.

Les langages de programmation fonctionnelle sont construits sur l’idée de composer des fonctions.

Alors, dans laquelle de ces catégories Python s’inscrit-il ?

En réalité, Python est un langage pragmatique qui mêle les styles orienté objet, fonctionnel et procédural, plutôt que d’adopter une approche puriste.

D’une part, cela permet à Python et à ses utilisateurs de sélectionner les bons aspects de différents paradigmes.

D’autre part, ce manque de pureté peut parfois prêter à confusion.

Heureusement, cette confusion est réduite au minimum si vous comprenez qu’à un niveau fondamental, Python est orienté objet.

Par cela, nous voulons dire qu’en Python, tout est objet.

Dans ce cours, nous expliquons ce que signifie cette affirmation et pourquoi elle est importante.

Nous utiliserons la bibliothèque tierce suivante

!pip install rich

Hide code cell output

Requirement already satisfied: rich in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (15.0.0)
Requirement already satisfied: markdown-it-py>=2.2.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from rich) (3.0.0)
Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from rich) (2.20.0)
Requirement already satisfied: mdurl~=0.1 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from markdown-it-py>=2.2.0->rich) (0.1.2)

6.2. Objets#

En Python, un objet est une collection de données et d’instructions conservées dans la mémoire de l’ordinateur, qui comprend

  1. un type

  2. une identité unique

  3. des données (c’est-à-dire du contenu)

  4. des méthodes

Ces concepts sont définis et discutés successivement ci-dessous.

6.2.1. Type#

Python propose différents types d’objets, afin d’accommoder différentes catégories de données.

Par exemple

s = 'This is a string'
type(s)
str
x = 42   # Créons maintenant un entier
type(x)
int

Le type d’un objet est important pour de nombreuses expressions.

Par exemple, l’opérateur d’addition entre deux chaînes de caractères signifie la concaténation

'300' + 'cc'
'300cc'

En revanche, entre deux nombres, il signifie l’addition ordinaire

300 + 400
700

Considérez l’expression suivante

'300' + 400
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[6], line 1
----> 1 '300' + 400

TypeError: can only concatenate str (not "int") to str

Ici, nous mélangeons les types, et il n’est pas clair pour Python si l’utilisateur souhaite

  • convertir '300' en entier puis l’ajouter à 400, ou

  • convertir 400 en chaîne puis la concaténer avec '300'

Certains langages pourraient tenter de deviner, mais Python est fortement typé

  • Le type est important, et la conversion de type implicite est rare.

  • Python répondra plutôt en levant une TypeError.

Pour éviter l’erreur, vous devez clarifier en modifiant le type concerné.

Par exemple,

int('300') + 400   # Pour additionner en tant que nombres, convertir la chaîne en entier
700

6.2.2. Identité#

En Python, chaque objet possède un identifiant unique, qui aide Python (et nous-mêmes) à suivre l’objet.

L’identité d’un objet peut être obtenue via la fonction id()

y = 2.5
z = 2.5
id(y)
140378057987888
id(z)
140378057994352

Dans cet exemple, y et z ont par hasard la même valeur (c’est-à-dire 2.5), mais ce ne sont pas le même objet.

L’identité d’un objet est en fait simplement l’adresse de l’objet en mémoire.

6.2.3. Contenu de l’objet : données et attributs#

Si nous posons x = 42, alors nous créons un objet de type int qui contient la donnée 42.

En fait, il contient davantage, comme le montre l’exemple suivant

x = 42
x
42
x.imag
0
x.__class__
int

Lorsque Python crée cet objet entier, il stocke avec lui diverses informations auxiliaires, telles que la partie imaginaire et le type.

Tout nom suivant un point est appelé un attribut de l’objet situé à gauche du point.

  • par exemple, imag et __class__ sont des attributs de x.

Nous voyons dans cet exemple que les objets possèdent des attributs contenant des informations auxiliaires.

Ils possèdent également des attributs qui agissent comme des fonctions, appelés méthodes.

Ces attributs sont importants, alors discutons-en en profondeur.

6.2.4. Méthodes#

Les méthodes sont des fonctions regroupées avec les objets.

Formellement, les méthodes sont des attributs d’objets qui sont appelables – c’est-à-dire des attributs qui peuvent être appelés comme des fonctions

x = ['foo', 'bar']
callable(x.append)
True
callable(x.__doc__)
False

Les méthodes agissent généralement sur les données contenues dans l’objet auquel elles appartiennent, ou combinent ces données avec d’autres données

x = ['a', 'b']
x.append('c')
s = 'This is a string'
s.upper()
'THIS IS A STRING'
s.lower()
'this is a string'
s.replace('This', 'That')
'That is a string'

Une grande partie des fonctionnalités de Python s’organise autour d’appels de méthodes.

Par exemple, considérez le morceau de code suivant

x = ['a', 'b']
x[0] = 'aa'  # Affectation d'élément via la notation entre crochets
x
['aa', 'b']

Il ne semble pas qu’aucune méthode ne soit utilisée ici, mais en fait la notation d’affectation entre crochets n’est qu’une interface pratique vers un appel de méthode.

Ce qui se passe réellement, c’est que Python appelle la méthode __setitem__, comme suit

x = ['a', 'b']
x.__setitem__(0, 'aa')  # Équivalent à x[0] = 'aa'
x
['aa', 'b']

(Si vous le souhaitiez, vous pourriez modifier la méthode __setitem__, de sorte que l’affectation entre crochets fasse quelque chose de totalement différent)

6.3. Inspection avec Rich#

Il existe un joli paquet appelé rich qui nous aide à visualiser le contenu d’un objet.

Par exemple,

from rich import inspect
x = 10
inspect(10)
╭────── <class 'int'> ───────╮
 int([x]) -> integer        
 int(x, base=10) -> integer 
                            
 ╭────────────────────────╮ 
  10                      
 ╰────────────────────────╯ 
                            
 denominator = 1            
        imag = 0            
   numerator = 10           
        real = 10           
╰────────────────────────────╯

Si nous voulons voir également les méthodes, nous pouvons utiliser

inspect(10, methods=True)
╭───────────────────────────────────────────────── <class 'int'> ─────────────────────────────────────────────────╮
 int([x]) -> integer                                                                                             
 int(x, base=10) -> integer                                                                                      
                                                                                                                 
 ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ 
  10                                                                                                           
 ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ 
                                                                                                                 
      denominator = 1                                                                                            
             imag = 0                                                                                            
        numerator = 10                                                                                           
             real = 10                                                                                           
 as_integer_ratio = def as_integer_ratio(): Return a pair of integers, whose ratio is equal to the original int. 
        bit_count = def bit_count(): Number of ones in the binary representation of the absolute value of self.  
       bit_length = def bit_length(): Number of bits necessary to represent self in binary.                      
        conjugate = def conjugate(): Returns self, the complex conjugate of any int.                             
       from_bytes = def from_bytes(bytes, byteorder='big', *, signed=False): Return the integer represented by   
                    the given array of bytes.                                                                    
       is_integer = def is_integer(): Returns True. Exists for duck type compatibility with float.is_integer.    
         to_bytes = def to_bytes(length=1, byteorder='big', *, signed=False): Return an array of bytes           
                    representing an integer.                                                                     
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

En fait, il existe encore plus de méthodes, comme vous pouvez le constater en exécutant inspect(10, all=True).

6.4. Un petit mystère#

Dans ce cours, nous avons affirmé que Python est, au fond, un langage orienté objet.

Mais voici un exemple qui semble plus procédural.

x = ['a', 'b']
m = len(x)
m
2

Si Python est orienté objet, pourquoi n’utilisons-nous pas x.len() ?

La réponse est liée au fait que Python vise la lisibilité et un style cohérent.

En Python, il est courant que les utilisateurs construisent des objets personnalisés — nous verrons comment faire cela plus tard.

Il est assez courant que les utilisateurs ajoutent à ceux-ci des méthodes qui mesurent la longueur de l’objet, définie de manière appropriée.

Lorsqu’il s’agit de nommer une telle méthode, les choix naturels sont len() et length().

Si certains utilisateurs choisissent len() et d’autres length(), alors le style sera incohérent et plus difficile à retenir.

Pour éviter cela, le créateur de Python a choisi d’ajouter len() comme fonction intégrée, afin de souligner que len() est la convention.

Ceci dit, Python reste néanmoins orienté objet en coulisses.

En effet, la liste x évoquée ci-dessus possède une méthode appelée __len__().

Tout ce que fait la fonction len(), c’est appeler cette méthode.

Autrement dit, le code suivant est équivalent :

x = ['a', 'b']
len(x)
2

et

x = ['a', 'b']
x.__len__()
2

6.5. Résumé#

Le message de ce cours est clair :

  • En Python, tout ce qui est en mémoire est traité comme un objet.

Cela inclut non seulement les listes, les chaînes de caractères, etc., mais aussi des choses moins évidentes, telles que

  • les fonctions (une fois qu’elles ont été chargées en mémoire)

  • les modules (idem)

  • les fichiers ouverts en lecture ou en écriture

  • les entiers, etc.

Se rappeler que tout est un objet vous aidera à interagir avec vos programmes et à écrire un code clair et pythonique.

6.6. Exercices#

Exercice 6.1

Nous avons déjà rencontré le type de données booléen précédemment.

En utilisant ce que nous avons appris dans ce cours, affichez une liste des méthodes de l’objet booléen True.