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()etpop()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
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
un type
une identité unique
des données (c’est-à-dire du contenu)
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, ouconvertir
400en 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,
imaget__class__sont des attributs dex.
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.
Indication
Vous pouvez utiliser callable() pour vérifier si un attribut d’un objet peut être appelé comme une fonction
Solution
Tout d’abord, nous devons trouver tous les attributs de True, ce qui peut être fait via
print(sorted(True.__dir__()))
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__getstate__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'is_integer', 'numerator', 'real', 'to_bytes']
ou
print(sorted(dir(True)))
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__getstate__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'is_integer', 'numerator', 'real', 'to_bytes']
Puisque le type de données booléen est un type primitif, vous pouvez aussi le trouver dans l’espace de noms intégré
print(dir(__builtins__.bool))
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__getstate__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'is_integer', 'numerator', 'real', 'to_bytes']
Ici, nous utilisons une boucle for pour filtrer les attributs qui sont appelables
attributes = dir(__builtins__.bool)
callablels = []
for attribute in attributes:
# Utilise eval() pour évaluer une chaîne comme une expression
if callable(eval(f'True.{attribute}')):
callablels.append(attribute)
print(callablels)
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__getstate__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'from_bytes', 'is_integer', 'to_bytes']