Mickaël Tits CETIC mickael.tits@cetic.be
Comme dans la plupart des langages de programmations, lorsqu'une instruction ne peut être correctement effectuée, l'interpréteur renvoie un message d'erreur, et s'arrête. On appelle ça une exception. Le message d'erreur est en général une source d'information importante pour le développeur, car il permet de comprendre le bug et ainsi de le résoudre.
On peut cependant contourner une exception pour permettre au programme de continuer, en définissant un comportement spécifique à adopter lorsqu'une exception survient.
#Exemple d'exception
x = 0
inverse = 1/x
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) <ipython-input-1-d2430f3c8e38> in <module>() 1 x = 0 ----> 2 inverse = 1/x ZeroDivisionError: division by zero
x = 0
try:
inverse = 1/x
except:
inverse = float("inf")
print(inverse)
inf
try:
inverse = 1/x
except Exception as e:
print(e)
inverse = float("inf")
print(inverse)
division by zero inf
try:
inverse = 1/y
except ZeroDivisionError as e:
inverse = float("inf")
print(inverse)
--------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-4-69970c7653df> in <module>() 1 try: ----> 2 inverse = 1/y 3 except ZeroDivisionError as e: 4 inverse = float("inf") 5 print(inverse) NameError: name 'y' is not defined
def my_function(x1, x2):
my_function
est l'identifiant de la fonction, et les éléments entre parenthèsex1, x2
sont les arguments de la fonction.
return
permet de renvoyer un résultat de la fonction et de sortir de la fonction. Ce mot-clé peut être utilisé plusieurs fois (par exemple pour retourner un résultat différent selon une condition).Exemple:
def my_function(x1, x2):
out = x1*x2
return out
On peut ensuite appeler une fonction de cette manière:
a = 6
b = 7
output = my_function(a, b)
Les variables a
et b
sont passées en arguments de la fonction my_function
, et le résultat de la fonction (42) est renvoyé. La variable output
est donc assignée à 42
.
def factorial(n):
"""
Cette fonction permet de calculer le factoriel de n
"""
f = 1
for i in range(1,n+1):
f = f * i
return f
print(factorial(2), factorial(3), factorial(20) )
2 6 2432902008176640000
x = 1
y = 2
def my_function():
print('x inside function =', x) #utilise la variable définie en-dehors de la fonction
try:
print(y)
except Exception as exc:
print(exc)
#print(y) #renvoie une erreur car une fonction locale du même nom est déclarée après
y = 4
print('y inside function =', y) #imprime la variable locale
z = 3
print('z inside function =', z) #imprime la variable locale
my_function()
print('x outside function =', x)
print('y outside function =', y)
try:
print(z)
except Exception as exc:
print(exc)
#print(z) # renvoie une erreur car la variable n'est pas définie en-dehors de la fonction
x inside function = 1 local variable 'y' referenced before assignment y inside function = 4 z inside function = 3 x outside function = 1 y outside function = 2 name 'z' is not defined
def my_function(param1, param2 = "param2", param3 = "param3", param4 = "param4"):
print(param1, param2, param3, param4)
try:
my_function()
except Exception as e:
print("Exception:", e)
my_function(1)
my_function(1, 2)
my_function(1, param3 = 2)
Exception: my_function() missing 1 required positional argument: 'param1' 1 param2 param3 param4 1 2 param3 param4 1 param2 2 param4
def square(x):
return x**2
def cube(x):
return x**3
my_list = [square, cube]
operands = [4,5]
operations = [my_list, operands]
print(operations)
print( [[func(op) for func in operations[0]] for op in operations[1]])
[[<function square at 0x7f33467afb70>, <function cube at 0x7f33467db048>], [4, 5]] [[16, 64], [25, 125]]
Les types de base, tel que les int, float, string, bool
sont immuables. Comme vu précédemment, les tuple
sont également immuables. Immuable signifie qu'après leur création, on ne peut pas modifier les objets en mémoire. C'est pourquoi le code suivant donne une erreur :
my_tuple = (1,2,3)
my_tuple[0] = 42
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-9-a6c2849be22a> in <module>() 1 my_tuple = (1,2,3) ----> 2 my_tuple[0] = 42 TypeError: 'tuple' object does not support item assignment
my_int1 = 1
#On crée une nouvelle étiquette (référence) vers l'objet 1
my_int2 = my_int1
#la fonction id renvoie l'adresse mémoire de l'objet (i.e. des données réellement stockées dans la mémoire RAM de l'ordinateur!)
#Rien ne sert de stocker plusieurs fois l'entier 1 en mémoire, c'est pourquoi les deux variables renvoie au même endroit.
print( my_int1, my_int2)
print( id(my_int1) , id(my_int2) )
#L'objet entier est immuable, donc lorsqu'on veut modifier my_int2, l'objet 1 n'est pas modifié en mémoire. A la place, l'étiquette "my_int2" est réassignée vers un nouvel objet en mémoire (en l'occurrence un objet de type entier et de valeur égale à 2)
my_int2 += 1
print( my_int1, my_int2)
print( id(my_int1) , id(my_int2) )
1 1 10968800 10968800 1 2 10968800 10968832
#Les tuples sont plus faciles (et rapides) à assigner
%timeit my_tuple = (1,2,3, 4, True, "hello" )
%timeit my_list = [1,2,3, 4, True, "hello" ]
The slowest run took 86.75 times longer than the fastest. This could mean that an intermediate result is being cached. 100000000 loops, best of 3: 12 ns per loop 10000000 loops, best of 3: 52.8 ns per loop
#Les tuples sont plus légers que les listes
import sys
my_tuple = (1,2,3, 4, True, "hello" )
my_list = list(my_tuple)
print(sys.getsizeof( my_tuple ) , sys.getsizeof( my_list ) )
96 136
A l'inverse, les list, dict, set
sont mutables, c'est-à-dire qu'on peut modifier l'objet lui-même.
Attention: lorsqu'on assigne une variable avec un objet mutable existant, cette variable devient une référence vers le même objet en mémoire! Ainsi, le code ci-dessous va modifier à la fois les variables my_list1
et my_list2
:
my_list1 = [1,2,3]
my_list2 = my_list1
my_list2[0] = 42
my_list1.append(4)
my_list2 += [5]
print(my_list1, my_list2)
[42, 2, 3, 4, 5] [42, 2, 3, 4, 5]
#L'opérateur "=" réassigne my_list2, ce qui crée donc une nouvelle liste (différente de my_list1)
my_list2 = my_list1+[6]
my_list2 += [7]
print(my_list1, my_list2)
[42, 2, 3, 4, 5] [42, 2, 3, 4, 5, 6, 7]
Lorsqu'on passe une variable en argument dans une fonction, c'est la référence de l'objet lui-même qui est passée à la fonction et non une copie (contrairement au langage C par exemple). ainsi, si l'objet passé en argument est mutable, celui-ci peut être modifié dans la fonction:
def modify(list_argument, int_argument):
list_argument+=[4]
int_argument += 4
print("inside:", list_argument, int_argument)
my_int = 2
my_list = [1,2,3]
modify(my_list, my_int)
#Un objet mutable est modifié en-dehors de la fonction, pas un objet immuable (puisque par définition ile ne peut être modifié)
print("outside:", my_list, my_int)
inside: [1, 2, 3, 4] 6 outside: [1, 2, 3, 4] 2
my_float = 4.2
help(my_float)
Help on float object: class float(object) | float(x) -> floating point number | | Convert a string or number to a floating point number, if possible. | | Methods defined here: | | __abs__(self, /) | abs(self) | | __add__(self, value, /) | Return self+value. | | __bool__(self, /) | self != 0 | | __divmod__(self, value, /) | Return divmod(self, value). | | __eq__(self, value, /) | Return self==value. | | __float__(self, /) | float(self) | | __floordiv__(self, value, /) | Return self//value. | | __format__(...) | float.__format__(format_spec) -> string | | Formats the float according to format_spec. | | __ge__(self, value, /) | Return self>=value. | | __getattribute__(self, name, /) | Return getattr(self, name). | | __getformat__(...) from builtins.type | float.__getformat__(typestr) -> string | | You probably don't want to use this function. It exists mainly to be | used in Python's test suite. | | typestr must be 'double' or 'float'. This function returns whichever of | 'unknown', 'IEEE, big-endian' or 'IEEE, little-endian' best describes the | format of floating point numbers used by the C type named by typestr. | | __getnewargs__(...) | | __gt__(self, value, /) | Return self>value. | | __hash__(self, /) | Return hash(self). | | __int__(self, /) | int(self) | | __le__(self, value, /) | Return self<=value. | | __lt__(self, value, /) | Return self<value. | | __mod__(self, value, /) | Return self%value. | | __mul__(self, value, /) | Return self*value. | | __ne__(self, value, /) | Return self!=value. | | __neg__(self, /) | -self | | __new__(*args, **kwargs) from builtins.type | Create and return a new object. See help(type) for accurate signature. | | __pos__(self, /) | +self | | __pow__(self, value, mod=None, /) | Return pow(self, value, mod). | | __radd__(self, value, /) | Return value+self. | | __rdivmod__(self, value, /) | Return divmod(value, self). | | __repr__(self, /) | Return repr(self). | | __rfloordiv__(self, value, /) | Return value//self. | | __rmod__(self, value, /) | Return value%self. | | __rmul__(self, value, /) | Return value*self. | | __round__(...) | Return the Integral closest to x, rounding half toward even. | When an argument is passed, work like built-in round(x, ndigits). | | __rpow__(self, value, mod=None, /) | Return pow(value, self, mod). | | __rsub__(self, value, /) | Return value-self. | | __rtruediv__(self, value, /) | Return value/self. | | __setformat__(...) from builtins.type | float.__setformat__(typestr, fmt) -> None | | You probably don't want to use this function. It exists mainly to be | used in Python's test suite. | | typestr must be 'double' or 'float'. fmt must be one of 'unknown', | 'IEEE, big-endian' or 'IEEE, little-endian', and in addition can only be | one of the latter two if it appears to match the underlying C reality. | | Override the automatic determination of C-level floating point type. | This affects how floats are converted to and from binary strings. | | __str__(self, /) | Return str(self). | | __sub__(self, value, /) | Return self-value. | | __truediv__(self, value, /) | Return self/value. | | __trunc__(...) | Return the Integral closest to x between 0 and x. | | as_integer_ratio(...) | float.as_integer_ratio() -> (int, int) | | Return a pair of integers, whose ratio is exactly equal to the original | float and with a positive denominator. | Raise OverflowError on infinities and a ValueError on NaNs. | | >>> (10.0).as_integer_ratio() | (10, 1) | >>> (0.0).as_integer_ratio() | (0, 1) | >>> (-.25).as_integer_ratio() | (-1, 4) | | conjugate(...) | Return self, the complex conjugate of any float. | | fromhex(...) from builtins.type | float.fromhex(string) -> float | | Create a floating-point number from a hexadecimal string. | >>> float.fromhex('0x1.ffffp10') | 2047.984375 | >>> float.fromhex('-0x1p-1074') | -5e-324 | | hex(...) | float.hex() -> string | | Return a hexadecimal representation of a floating-point number. | >>> (-0.1).hex() | '-0x1.999999999999ap-4' | >>> 3.14159.hex() | '0x1.921f9f01b866ep+1' | | is_integer(...) | Return True if the float is an integer. | | ---------------------------------------------------------------------- | Data descriptors defined here: | | imag | the imaginary part of a complex number | | real | the real part of a complex number
#Attributs d'un float
print(my_float.real, my_float.imag)
4.2 0.0
#Une méthode d'un float
my_float = 4.0
my_float.is_integer()
True
print(dir(my_list))
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
my_list.index(2, 5)
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-20-70a69dcc2203> in <module>() ----> 1 my_list.index(2, 5) ValueError: 2 is not in list
Les classes permettent d'encapsuler les différents attributs et méthodes dans une seule entité. L'encapsulation du code permet de rendre le code plus clair, et plus modulaire:
class House:
"""
Cette classe permet de définir les propriétés d'une maison, donc l'adresse, le prix, la surface et le nombre de chambre.
Elle permet de vérifier la validité desdonnées (validate()), d'afficher les informations sur la maison (display()), et de calculer le prix par m2 (compute_price_m2())
"""
def __init__(self, address, price, surface, rooms):
self.address = address
self.price = price
self.surface = surface
self.rooms = rooms
#On vérifie la validité des données, et on les rend valides si possible, sinon on renvoie False (pas valide)
self.is_valid = self.validate()
def compute_price_m2(self):
return self.price/self.surface
def validate(self):
"""
Vérifie si l'objet est valide
"""
#Si le prix, la surface, ou le nombre de chambre ne sont pas des int, on essaye de les convertir, sinon pas valide
try:
self.price = int(self.price)
self.surface = int(self.surface)
self.rooms = int(self.rooms)
return True
except:
return False
def display(self):
if self.is_valid:
print(self.address, ":", self.price, "€, ", self.surface, "m2", self.rooms, "rooms")
else:
print("L'élément n'est pas un élément valide:", self.address)
house1 = House("Rue de Bruxelles 42, Namur", 300000, 120, 3)
house1.display()
#appeler un attribut (pas de parenthèses)
print(house1.price)
#appeler une méthode (parenthèses nécessaires)
print(house1.compute_price_m2())
Rue de Bruxelles 42, Namur : 300000 €, 120 m2 3 rooms 300000 2500.0
Remarque: On pourrait définir chaque méthode comme une fonction en-dehors du cadre de la classe, mais ça n'a pas d'intérêt puisque le processus est spécifique à la classe House. De plus, ça rend le code moins clair et rend possible une utilisation non-conforme, ce qui amène à des bugs:
def display(my_house):
if my_house.is_valid:
print(my_house.address, ":", my_house.price, "€, ", my_house.surface, "m2", my_house.rooms, "rooms")
else:
print("L'élément n'est pas un élément valide:", my_house.address)
display(house1)
Rue de Bruxelles 42, Namur : 300000 €, 120 m2 3 rooms
display([1,2,3])
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-24-0156a3a90e5d> in <module>() ----> 1 display([1,2,3]) <ipython-input-23-2aee96ced0e7> in display(my_house) 1 def display(my_house): 2 ----> 3 if my_house.is_valid: 4 print(my_house.address, ":", my_house.price, "€, ", my_house.surface, "m2", my_house.rooms, "rooms") 5 else: AttributeError: 'list' object has no attribute 'is_valid'
#Attention, par défaut les classes sont mutables
house2 = house1
house2.price = 350000
print(house1.price)
350000
#pour créer une copie d'un objet mutable, on peut utiliser un module Python: copy.copy
from copy import copy
house2 = copy(house1)
house2.price = 500000
print(house1.price)
350000
Remarque: On peut tout à fait ajouter un attribut à un objet créé à partir d'une classe. Ce n'est cependant pas conseillé car les objets d'une même classe perdent alors leur homogénéité.
house2.floors = 2
print(house2.floors)
2
#L'hétérogénéité des objets d'une même classe rendent le programme plus compliqué, diminuent la clarté et augmentent les risques de bugs
print(house1.floors)
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-28-29a735c6a59c> in <module>() ----> 1 print(house1.floors) AttributeError: 'House' object has no attribute 'floors'
Maintenant que vous connaissez les concepts principaux du langage Python, vous pouvez passez au Chapitre 4: Un exemple concret: analysons quelques biens immobiliers...