Objets et références
Objets et références en Python
Frédéric Herbreteau, Bordeaux INP/LaBRI (frederic.herbreteau@bordeaux-inp.fr)
Classes et objets
Importance du typage
Représentation d’un nombre rationnel par un tuple
>>> r1 = (3, 4) # 3/4
>>> def eval(r):
... return r[0]/r[1]
>>> eval(r1)
0.75
>>> t1 = (1,0)
>>> eval(t1)
...
ZeroDivisionError: division by zero
>>> t2 = (1,)
>>> eval(t2)
...
IndexError: tuple index out of range
Qu’est-ce qu’un type?
Classes et objets
- classe: structure de données + méthodes permettant de les manipuler
class Rational:
def print(self):
...
- objet: instance d’une classe
>>> r = Rational(4, 5) # 4/5
>>> r.print()
- abstraction:
- la structure est uniquement manipulée via ses méthodes
- bonne pratique: pas d’accès direct aux champs débutant par
_
Construction d’un objet (méthode __init__)
- allocation mémoire pour stocker l’instance (gérée automatiquement)
- initialisation de l’instance courante
selfdans un état cohérent
class Rational:
def __init__(self, n=0, d=1):
assert d > 0
self._n = n # crée la variable d'instance `_n` et l'initialise
self._d = d # crée la variable d'instance `_d` et l'initialise
>>> r1 = Rational() # 0/1
>>> r2 = Rational(2, 3) # 2/3
>>> r2
<__main__.Rational object at 0x1017c7890>
NB: l’instance est crée par __new__ qui dépasse le cadre de ce cours
Définir une méthode
- fonction définie dans la classe
- dont le premier paramètre est l’instance
self
class Rational:
def __init__(self, n=0, d=1):
assert d > 0
self._n = n
self._d = d
def print(self):
print(f'{self._n}/{self._d}')
>>> r1 = Rational(4, 6)
>>> r1.print()
4/6
Opérateurs et méthodes
- Les opérateurs appellent des méthodes particulières: les hooks
>>> 3 + 2
5
>>> int(3).__add__(2)
5
>>> l = [1, 2, 3]
>>> l + 4
[1,2,3,4]
>>> l.__add__([4])
[1,2,3,4]
>>> 'abc' + 'de'
'abcde'
>>> 'abc'.__add__('de')
'abcde'
Définir un opérateur
- le hook
__add__permet de définir l’opérateur+
class Rational:
...
def __add__(self, r):
return Rational(self._n * r._d + r._n * self._d, self._d * r._d)
>>> r1 = Rational(2, 3)
>>> r2 = Rational(4, 5)
>>> r3 = r1 + r2
>>> r3.print()
22/15
Affichage des objets
- le hook
__str__définit la représentation de l’objet commestr - le hook
__repr__fournit une représentationstrinterprétable pareval
class Rational:
...
def __str__(self):
return f'{self._n}/{self._d}'
def __repr__(self):
return f'Rational({self._n}, {self._d})'
>>> r1 = Rational(2,3)
>>> print(r1) # appelle __str__
2/3
>>> r1 # appelle __repr__
Rational(2,3)
Documentation des objets
- lister les méthodes d’un objet ou d’une classe avec
dir
>>> x = 'abc'
>>> dir(x)
>>> x.upper()
'ABC'
>>> x = 3
>>> dir(x)
>>> x.bit_count()
2
- documentation avec
help
help(x)
help(x.bit_count)
Variables et références
Variables et références
- Les variables stockent une référence vers un objet
- les valeurs sont typées, mais les variables ne sont pas typées
>>> x = 1
>>> l = [1,2,3]
>>> s = {'a', 'b', 'c'}
Types muables et immuables
- les types
int,bool,float,strettuplesont immuables - les opérations créent une nouvelle valeur (affectation mémoire)
>>> x = 1
>>> x+1
list,set,dict, … et les types définis sont muables- certaines opérations modifient l’objet en place
>>> l1 = [1,2,3]
>>> l1.append(4)
Affectations et références
- l’affectation d’une valeur à une variable modifie la référence
>>> x = 1
>>> x = x+1
>>> l1 = [1,2,3]
>>> l1 = [1]
Références et partage d’objets
- plusieurs variables peuvent référencer le même objet (aliasing)
- l’affectation crée de l’aliasing
>>> a = []
>>> b = a # b est un alias de a
>>> a.append(1)
>>> b
[1]
Cloner un objet pour éviter l’aliasing (module copy)
- copie superficielle:
copy.copy()et copie profonde:copy.deepcopy(), ainsi que méthodecopypour certaines classes (list,dict,…) - redéfinir les hooks
__copy__et__deepcopy__au besoin
>>> a = [[]]
>>> b = copy.copy(a) # b est une copie superficielle de a (ou b = a.copy())
>>> id(b) == id(a) # False
>>> id(b[0]) == id(a[0]) # True
>>> c = copy.deepcopy(a) # c est une copie profonde de a
>>> id(c) == id(a) # False
>>> id(c[0]) == id(a[0]) # False
>>> a[0].append(1)
>>> a # [[1]]
>>> b # [[1]]
>>> c # [[]]
Arguments des fonctions et référence
- les paramètres de fonction sont passés par référence
- entraîne l’aliasing des arguments
- modification d’objets muables par effet de bord
>>> def f(l):
... l.append(3)
...
>>> l1 = [1,2]
>>> f(l1)
>>> l1
[1,2,3]
Arguments et affectation
- pourquoi les fonctions
fci-dessous ne modifient-t-elles pas leurs arguments?
>>> def f(x):
... x = x+1
>>> x1 = 2
>>> f(x1)
>>> x1
2
>>> def f(l):
... l = [1]
>>> l1 = [1,2,3]
>>> f(l1)
>>> l1
[1,2,3]