Par Bertrand Bordage

  • Programmeur Python, Django, PostgreSQL
  • Membre de l’équipe de développement du CMS Wagtail
  • Créateur de nombreux projets open source plus ou moins célèbres

Projets pour les vétérans

Choisir un des projets suivants :

  • Créer une mini application web avec Django faisant une interface un minimum jolie et colorée de l’action RPG textuel (voir dernières slides du cours). Stocker l’état d’une partie dans la base de données.
  • Écrire un programme qui trouve des anagrammes parfaits en français. Par exemple, « mec menu anormal » est un anagramme de « Emmanuel Macron ».
  • Réaliser un site avec catalogue d’articles en vente en utilisant Wagtail, avec un design pas trop moche en utilisant Bootstrap. S’inspirer éventuellement de la bakerydemo.

Introduction

Créé en 1991 par Guido Van Rossum, un fan des Monty Python.

Plus ancien que PHP, Java, JavaScript, .Net

Pourtant bien plus simple et (discutablement) plus complet

Python, un langage impératif (on lui donne des ordres), interprété (on ne compile pas) à typage dynamique (on ne précise pas les types aux variables)

⇒ 10 × plus rapide pour coder qu’avec un langage compilé à typage statique comme C

Langage multi-paradigme, on peut programmer de nombreuses manières : programmation fonctionnelle, orientée objet, orientée événements, asynchrone, etc.

Un langage pour se concentrer sur ce qu’on cherche à faire, en étant le moins possible perturbé par des contraintes techniques

Une des communautés open source les plus actives, compétentes et bienveillantes.

Pourquoi utiliser Python

Facile pour débuter, mais reste pertinent pour des travaux experts

Permet d’écrire tout type de programme

Contient une librairie standard particulièrement complète

Possède un écosystème de paquets extrêmement vaste pour réaliser les tâches les plus complexes

Basé sur une philosophie incitant à la simplicité et à la clarté.

Un poème, Le Zen de Python, résume cette philosophie.

Et open source, donc :

  • gratuit
  • on peut voir comment c’est construit
  • on peut le modifier voire y participer

Que faire avec Python

Utilisé dans le monde entier comme couteau suisse de la programmation. Second langage le plus utilisé en open source après JavaScript.

On s’en sert pour faire :

  • des scripts pour automatiser des tâches
  • des programmes divers dans les systèmes d’exploitation Linux & Mac OS X
  • des applications web
  • du traitement de données : stockage, data mining, acoustique, reconnaissance d’image, deep learning, etc.
  • de la recherche scientifique : physique, biologie, météorologie, mathématiques, etc.
  • de la domotique et autres systèmes embarqués
  • parfois pour faire des jeux vidéo

Commence à être indispensable en informatique aujourd’hui, et est utilisé par les plus grands :

  • Google
  • NASA
  • Lucasfilm
  • Dropbox, Instagram, Mozilla, Pinterest, etc.

Comment installer Python

On utilise au minimum la version 3.4.
Prendre la version la plus récente, par exemple 3.6.4.

Ubuntu ou Debian

Ouvrir un terminal et lancer :

sudo apt install python3.6

Si le paquet n’existe pas vous devez utiliser une version plus ancienne, essayer en remplaçant 3.6 par 3.5 ou 3.4.

macOS

Télécharger sur python.org puis installer le programme.

Windows

Télécharger sur python.org puis lancer le fichier téléchargé, et penser à cocher Add Python 3.6 to PATH pendant l’installation. Redémarrer l’ordinateur parce que Windows.

Comment utiliser Python

Quelque soit le système, lancer un terminal (nommé « Invite de commandes » sous Windows).

Pour lancer Python en mode interactif, utiliser la commande :

python3.6

Après plusieurs lignes d’informations, on voit un triple chevron >>>.
Cela indique que Python attend que vous saisissiez une commande Python.

Plus tard, on créera des fichiers .py contenant du Python, qu’on exécutera en mode non-interactif en faisant :

python3.6 un_programme.py

Débutons

Glossaire

Python utilise de nombreux termes, et ce n’est jamais à la légère. Chaque terme a une définition bien précise. Quelques exemples :

  • séquence
  • itérateur
  • générateur
  • métaclasse
  • compréhension de liste
  • décorateur
  • hashable
  • […]

En cas de toute sur un terme technique, se référer systématiquement au glossaire officiel.

Les types de base

Les données manipulées en Python ont des types divers.
Des dizaines de types, mais nous nous servirons d’abord des types simples suivants :

Nom du type en Python Notation
Nombre entier int 1234567
Nombre à virgule float 3.14
Chaîne de caractères str 'abc' ou "abc"
Chaîne de caractères binaire bytes b'abc' ou b"abc"
Booléen (« oui » ou « non ») bool True ou False
Valeur spéciale unique pour dire « rien » NoneType None

Attention : les nombres à virgule s’écrivent… sans virgule ! Il s’agit de la notation anglo-saxonne, avec un point à la place de la virgule.

Exercice 1

Dans la console interactive :

  • créer un nombre entier quelconque
  • créer un nombre entier supérieur à 1 000.
  • créer un nombre à virgule quelconque
  • créer une chaîne de caractères quelconque
  • créer un booléen quelconque

Les opérations

En Python comme en mathématiques, des symboles existent pour effectuer des opérations.
Heureusement, ils sont beaucoup moins nombreux, et certains sont déjà familiers.
Ces opérations s’exécutent indifféremment sur des nombres entiers ou à virgule.

Opération Notation Résultat
Addition 7 + 2 9
Soustraction 7 - 2 5
Multiplication 7 * 2 14
Division 7 / 2 3.5
Modulo (reste d’une division euclidienne) 7 % 2 1
Quotient d’une division euclidienne 7 // 2 3
Puissance 7 ** 2 49

Pour regrouper des opérations, on utilise des parenthèses, comme en mathématiques :
((3 + 8) * (5.5 - 7)) / 3

Exercice 2

Dans la console interactive :

  • additionner deux nombres entiers quelconques
  • soustraire un nombre à virgule à un nombre entier
  • multiplier un nombre entier par l’addition de deux nombres à virgule, et diviser le tout par un nombre entier

Opérations & types

En Python, ces opérations arithmétiques fonctionnent aussi sur d’autres types.
Certains opérateurs marchent notamment sur les chaînes de caractères.

Voici ce que cela donne :

Opération Résultat
'abc' + 'def' 'abcdef'
'abc' - 'def' Opération impossible !
'abc' * 'def' Opération impossible !
'abc' * 3 'abcabcabc'
'abc' / 'def' Opération impossible !
'abc' / 3 Opération impossible !

(Toujours dans la console interactive)

Exercice 3

« Concaténer » (rassembler) 3 chaînes de caractères :

  • votre prénom
  • une espace
  • votre nom

Exercice 4

Écrire « blablablablablabla » (6 fois « bla ») en 7 caractères

Les variables

En informatique, une variable est un endroit nommé où on stocke une donnée.
En Python, on écrit d’abord le nom de la variable, puis le symbole =, puis la donnée :

nom = 'Vivaldi'

On dit alors qu’on assigne la chaîne de caractères 'Vivaldi' à la variable nom.

Attention, ce n’est pas une égalité mathématique ! On peut très bien écrire :

n = 1
n = 2

Cela signifie qu’on assigne 1 à la variable n, puis qu’on assigne 2 à cette même variable. Après la seconde ligne, n vaut 2, cela écrase l’opération précédente, comme lorsqu’on écrase un fichier avec un fichier du même nom.

Un nom de variable peut contenir des lettres, des chiffres ou des underscores mais ne peut commencer par un chiffre. Il peut stocker le résultat d’un calcul. Exemple :

mon_budget_2016 = (12 * 30) + 5

À noter, dans les dernière versions de Python, on peut préciser un type aux variables à titre informatif :

n: int = 3

Ne pas hésiter à le faire si cela vous aide, mais vous pouvez vous en passer.

(Toujours dans la console interactive)

Exercice 5

Assigner le titre de votre film préféré à une variable film_favori

Exercice 6

Assigner le résultat d’une multiplication de nombres entiers à une variable

Utiliser des fonctions

Comme en mathématiques, une fonction en programmation permet de transformer des paramètres en un résultat. Pour utiliser une fonction, il faut écrire le nom de la fonction suivi de parenthèses contenant des paramètres séparés par des virgules. Exemple :

print(1, 2, 'abc')

Cette fonction, print, permet d’afficher les paramètres dans la console.
À noter, cette fonction est en réalité ce qu’on appelle une procédure en algorithmique, terme que l’on n’utilise pas en Python.

Une autre fonction, input, permet de demander à l’utilisateur de saisir du texte, qu’on peut stocker :

desir = input('Que voulez-vous ? ')

Fonctions utiles

Les fonctions utilisables directement sont toutes listées sur docs.python.org/3/library/functions.

Seules ces fonctions sont utiles pour commencer :

Fonction Utilité Fonction Utilité
float Convertit le paramètre en nombre à virgule list Convertit une donnée en liste
input Voir précédemment min Donne le plus petit de tous les paramètres
int Convertit le paramètre en nombre entier max Donne le plus grand de tous les paramètres
len Renvoie la longueur d’une donnée print Voir précédemment

(Toujours dans la console interactive)

Exercice 7

Effectuer les tâches suivantes :

  • afficher « Bienvenue »
  • demander l’âge de l’utilisateur en posant la question « Quel âge avez-vous ? » et le stocker dans une variable age
  • convertir l’âge en nombre entier et le multiplier par 4, stocker le tout dans une variable futur_age
  • afficher « Tu imagines quand tu auras X ans ? » en remplaçant X par futur_age

Les listes

Les listes permettent de stocker des séries ordonnées de données, et se définissent avec des crochets :

premiers = [1, 2, 3, 5, 7, 11]
liste_mixte = [1, 1.5, 'a', True, None]

Lire une liste

Connaître le nombre d’éléments d’une liste en utilisant la fonction len vue auparavant :

len(premiers)  # Renvoie 6

Obtenir un élément de la liste (en commençant par 0 et non 1) :

premiers[0]  # Renvoie 1
premiers[3]  # Renvoie 5

Tester la présence d’un élément :

'violon' in liste_mixte  # Renvoie False

Les listes — suite

Modifier une liste

Redéfinir un élément de la liste :

liste_mixte[4] = 53.42

Ajouter des éléments à la liste :

premiers += [13, 17]  # Raccourci pour premiers = premiers + [13, 17]

Supprimer un élément de la liste :

del premiers[0]

(Toujours dans la console interactive)

Exercice 8

Effectuer les instructions suivantes :

  • créer une liste contenant 3 données quelconques
  • afficher sa longueur pour vérifier qu’elle vaut bien 3
  • aller chercher le 2nd élément
  • remplacer le premier élément de cette liste par 'bravo'
  • ajouter 2 éléments quelconques
  • supprimer le dernier élément
  • vérifier si 'Chuck Norris' est dans la liste (on ne sait jamais…)
  • afficher la liste

Les dictionnaires

Comme une liste, mais pour faire correspondre des données entre elles.
Ici, on fait correspondre des prénoms (les clés) à des âges (les valeurs) :

ages = {'Paul': 38, 'Aziz': 49, 'Shotaro': 22}

Connaître le nombre d’entrées :

len(ages)  # Renvoie 3

Obtenir une valeur à partir d’une clé :

ages['Paul']  # Renvoie 38

Ajouter une entrée au dictionnaire :

ages['Antonio'] = 63

Supprimer une entrée du dictionnaire :

del ages['Paul']

Vérifier si une clé est dans le dictionnaire :

'Hans' in ages  # Renvoie False

(Toujours dans la console interactive)

Exercice 9

Effectuer les instructions suivantes :

  • créer un dictionnaire vide dans une variable achats, dans lequel on fera correspondre des noms d’objets à des nombres à virgule indiquant le prix d’achat d’un objet.
  • ajouter un ordinateur à 700 € au dictionnaire
  • ajouter une baguette à 75 centimes
  • remplacer le prix de l’ordinateur par 800 €
  • aller chercher le prix de l’ordinateur
  • supprimer la baguette
  • vérifier si Chuck Norris est dans le dictionnaire (on ne sait jamais…)

Immutable vs mutable

En Python, les types de données se classent en deux grandes catégories :

  • Les immutables ne peuvent pas être modifiés directement. Il est impossible de modifier un immutable « en place ».
  • Les mutables sont modifiables directement sans créer de copie de la donnée d’origine.

Exemple :


In [1]:
l = [1, 2, 3]
print(id(l), l)  # id renvoie l’adresse mémoire de l’objet
del l[0]
print(id(l), l)  # L’adresse mémoire est la même, c’est le même objet


140347087196936 [1, 2, 3]
140347087196936 [2, 3]

In [2]:
s = 'abc'
print(id(s), s)
s += 'def'
print(id(s), s)  # L’adresse mémoire est différente, ce n’est pas le même


140347219328056 abc
140347052631072 abcdef

Immutable vs mutable — suite

Voici un récapilatif des types, selon s’ils sont mutables ou immutables. Sur une même ligne figurent les équivalents. Par exemple, str n’a pas d’équivalent mutable. list a l’équivalent immutable tuple. dict n’a pas d’équivalent immutable.

Immutable Mutable
str
bytes
int
float
bool
NoneType
tuple list
dict
frozenset set

tuple, frozenset et set sont des types moins souvent utilisés pour débuter Python. Un tuple s’écrit (1, 2, 3) et est comme une liste non modifiable. Un set est un ensemble de données qui supprime automatiquement les doublons et s’écrit set([1, 2, 3]). frozenset fonctionne pareil, mais n’est pas modifiable.

Les conditions

Pour exécuter du code uniquement si une condition est vraie, on utilise la commande if suivie de la condition. Par exemple ici, « bingo » ne sera affiché que si x est supérieur à 3 :

if x > 3:
    print('bingo')

Notez qu’on indente de 4 espaces ce qui est exécuté si la commande est vraie.
Deux types d’opérateurs pour les conditions, les comparaisons et opérateurs booléens (ou combinaisons).

Type d’opération Opération Notation
Comparaison a égal à b a == b
Comparaison a différent de b a != b
Comparaison a supérieur à b a > b
Comparaison a supérieur ou égal à b a >= b
Comparaison a inférieur à b a < b
Comparaison a inférieur ou égal à b a <= b
Combinaison a ou b a or b
Combinaison a et b a and b
Combinaison opposé de a not a

Les conditions — suite

Les comparaisons prennent deux valeurs quelconques, les opérandes, et renvoient un booléen, par exemple :

3 > 1       # renvoie True
'a' == 'b'  # renvoie False

Les combinaisons permettent de combiner des booléens, donc de combiner le résultat de comparaisons :

3 > 1 or 'a' == 'a'  # renvoie True puisque l’une des deux comparaisons vaut True

On peut mettre des parenthèses pour expliquer à Python la priorité des opérations :

(3 > 1 or 'a' == 'a') and 5 <= 2  # renvoie False, tandis que
3 > 1 or ('a' == 'a' and 5 <= 2)  # renvoie True

Voici un résumé du comportement des opérateurs booléens :

True or True     # True
True or False    # True
False or True    # True
False or False   # False
True and True    # True
True and False   # False
False and True   # False
False and False  # False
not True         # False
not False        # True

Les conditions — suite

Si la condition d’un if est False, on peut aussi exécuter autre chose, à l’aide du mot-clé else :


In [3]:
if 1 > 3:
    print('Condition vraie')
else:
    print('Condition fausse')


Condition fausse

On peut aussi tester une autre condition à l’aide de la contraction de “else if”, le mot-clé elif :


In [4]:
if 1 > 3:
    print('1re condition vraie')
elif 2 > 3:
    print('2de condition vraie')
elif 5 > 3:
    print('3e condition vraie')
else:
    print('Tout est faux')


3e condition vraie

Exercice 10

Créer un fichier texte vide conditions.py

À l’intérieur du fichier, créer une variable age contenant votre âge

Créer une variable pointure contenant votre pointure de chaussure

Écrire une condition affichant « Vous êtes mineur » si vous avez moins de 18 ans

Sinon vérifier si votre pointure vaut plus de 2 fois votre âge et que votre âge est pair, et si tout cela est vrai, afficher « Vous êtes un majeur veinard »

Sinon afficher « Vous êtes un majeur et malchanceux »

Les boucles

Deux types de boucles en Python : for et while.

  • while répète les mêmes lignes de code tant qu’une condition reste vraie
  • for passe en revue le contenu d’une liste (ou similaire) et s’arrête lorsqu’il n’y a plus rien

Deux exemples équivalents affichant tous deux 0, 1 et 2 :

i = 0
while i < 3:
    print(i)
    i += 1  # Raccourci pour i = i + 1
for i in [0, 1, 2]:
    print(i)

for est très prisé dans l’univers Python pour sa simplicité, mais dans certains cas while est plus adapté.

Il existe une fonction range permettant d’écrire plus rapidement une suite de nombres consécutifs dans une boucle for. Par exemple, range(10) équivaudra à itérer sur [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] et range(3, 6) équivaudra à itérer sur [3, 4, 5].

Exercice 11

Dans un fichier boucles.py, créer deux boucles :

Une boucle affichant des nombres de 50 à 100 inclus

Une boucle demandant à l’utilisateur « Quel est votre prénom ? » jusqu’à ce qu’il tape exactement votre prénom.
À chaque fois que cela ne correspondra pas à votre prénom, afficher « Accès refusé »

Utiliser des modules

Python inclut des dizaines de modules indispensables à la vie du développeur.

Pour importer un module entier et s’en servir, il faut utiliser la syntaxe :

import random
random.randint(5, 20)  # Choisit un nombre aléatoire entre 5 et 20.

Pour importer directement un composant d’un module, comme une fonction, il faut utiliser la syntaxe :

from random import randint
randint(5, 20)

Par convention, toutes les importations sont faites en haut du fichier et sont groupées par ordre alphabétique de nom de module, par exemple :

from math import log
from random import randint, choice
import re
from time import time

La documentation de tous les modules inclus dans Python est sur :
docs.python.org/3/library/

Passons à la pratique :

le jeu du juste prix !

Le jeu du juste prix

Deviner le prix d’un objet pioché aléatoirement.
Plusieurs objets et leurs fourchettes de prix sont précisées dans le code.
Le prix à deviner est aléatoire au sein de la fourchette de prix de l’objet.

Écosystème Python

Python 2 vs 3

Depuis 8 ans, deux versions principales de Python coexistent : Python 2 et Python 3.

Python 3 est né de la volonté de changer de nombreux choix de Python qui ne pouvaient être changés du jour au lendemain.
Cette coexistence a permi à la communauté de prendre le temps de s’approprier tous ces changements et de rendre les paquets compatibles avec la nouvelle version, Python 3.

Aujourd’hui, nous approchons de la fin de cette période de transition et vous pouvez travailler directement avec Python 3, peut-être n’aurez-vous d’ailleurs jamais à utiliser Python 2.

Si jamais vous avez besoin d’utiliser Python 2, les différences majeures sont :

  • print n’était pas une fonction et s’écrivait donc sans parenthèses : print 'quelque chose'
  • Les chaînes de caractères étaient par défaut en ASCII, il fallait indiquer l’encodage en haut du fichier avec une première ligne # coding: utf-8 et ajouter un petit U avant chaque chaîne unicode, comme u'chaîne accentuée'
  • La division de deux nombres entiers renvoyait un nombre entier, donc 1 / 2 renvoyait 0 et non 0.5
  • input avait un comportement différent et l’équivalent du input de Python 3 s’appellait raw_input

Les autres différences sont assez avancées et il est finalement rare de les croiser.

Le virtualenv

En informatique, on est amenés à utiliser des versions différentes de logiciels pour chaque projet.
Un environnement virtuel Python, ou “virtualenv”, permet d’avoir pour chaque projet un ensemble de paquets Python différent des autres projets.

Installation sous Ubuntu :

sudo apt install virtualenvwrapper python3-virtualenv
echo 'export WORKON_HOME=~/.virtualenvs' >> ~/.bashrc

Redémarrer la console

Créer un virtualenv :

mkvirtualenv -p /usr/bin/python3.5 nom_du_virtualenv

Activer un virtualenv :

workon nom_du_virtualenv

Désactiver le virtualenv actuel :

deactivate

Supprimer un virtualenv :

rmvirtualenv nom_du_virtualenv

Installer des librairies avec pip

Une fois dans un virtualenv, la commande pip permet d’installer/désinstaller des paquets. Installer puis désinstaller un paquet :

pip install Django
pip uninstall Django

Lister les paquets installés :

pip freeze

Installer une version spécifique d’un paquet (on échappe avec des guillemets car on utilise souvent des caractères spéciaux de ligne de commande pour préciser la version) :

pip install "django-cachalot==1.5.0"

Installer un paquet avec la dernière version dans une fourchette de versions :

pip install "Django>=1.10,<1.11"

Rechercher tous les paquets contenant un mot dans leur nom :

pip search django

Tous ces paquets proviennent du PyPI (Python Package Index) : pypi.python.org

(Fin de la parenthèse écosystème)

Créer une fonction

Une fonction permet d’éviter de répéter du code.
⇒ En créer une dès qu’on sent qu’on écrit à peu près la même chose.

Une fonction se définit ainsi :


In [5]:
def calcul(x, y):
    return x ** 2 + 3 * y + 2
  • Le mot-clé def indique qu’on définit une fonction.
  • Son nom est ici calcul.
  • Ses arguments (ou paramètres) sont x et y : comme des variables, mais définies implicitement.
  • Le code qu’elle contient est indenté.
  • Elle renvoie le résultat du calcul $x^2 + 3y + 2$ à l’aide du mot-clé return.

Créer une fonction — suite

Exemple d’utilisation :


In [6]:
print(calcul(5, 23))


96

Une fonction peut :

  • contenir plusieurs lignes de code
  • contenir n’importe quelle ligne de code Python (assignation de variable, condition, boucle, etc.)
  • contenir des arguments optionnels, qui doivent avoir une valeur par défaut
  • ne rien renvoyer avec return, elle renverra None par défaut (ce qu’on appelle procédure en algorithmique est donc appellé aussi fonction en Python)

Créer une fonction — suite

Exemple plus complexe :

def calcul_dur(a, b=0, c=1):
    if b == 0:
        return a
    d = a / b + c
    return d * 2

Ici, b et c sont des arguments optionnels, si on ne les spécifie pas ils vaudront 0 et 1. Exemples d’utilisation :

calcul_dur(1)            # Renvoie 1
calcul_dur(1, 2, 3)      # Renvoie 7.0
calcul_dur(2, b=3, c=4)  # Renvoie 9.3333…
calcul_dur(2, c=4)       # Renvoie 2
calcul_dur(2, c=4, b=3)  # Renvoie 9.3333…

On peut donc préciser le nom des arguments optionnels pour :

  • être plus explicite
  • sauter des arguments optionnels
  • spécifier des arguments optionnels dans l’ordre qu’on veut

À partir de maintenant, toujours faire les exercices dans des fichiers Python, ici par exemple fonctions.py.

Exercice 12

Créer une fonction square transformant un entier x en son carré.

Exercice 13

Créer une fonction salut avec un argument prenom et affichant « Salut Michel ! Ça va ? » en remplaçant le prénom.

Modifier la fonction précédente pour qu’elle accepte un argument optionnel booléen forme_politesse permettant de déterminer si on écrit « Salut Michel ! Ça va ? » ou « Bonjour Michel ! Comment allez-vous ? ».
Par défaut, afficher la forme de politesse.

Ajouter un argument optionnel chaîne de caractères titre contenant par exemple 'M.', 'Mme', 'Dr', 'mon' ou 'ma'. Par défaut, cet argument vaut None. Le titre doit s’afficher de sorte que les chaînes affichent « Bonjour Dr. Michel ! Comment allez-vous ? » ou « Salut mon Michel ! Ça va ? ». Si cet argument vaut None, on affiche la forme du point précédent : « Salut Michel ! Ça va ? ».

Utiliser la fonction pour afficher :
« Bonjour M. Pierre ! Comment allez-vous ? »
« Salut cher Patrice ! Ça va ? »
« Salut Jean-Claude ! Ça va ? »

Créer une fonction — arguments illimités

Une fonction Python peut accepter n’importe quel nombre d’arguments.

Arguments positionnels

Pour une quantité illimitée d’arguments positionnels (f(a, b, c, …)) :


In [7]:
def f(*args):
    print(args)

f(1, 2, 3)


(1, 2, 3)

Créer une fonction — arguments illimités

Pour une quantité illimitée d’arguments optionnels (f(a=1, b=2, c=3, …)) dits keyword args ou kwargs :


In [8]:
def f(**kwargs):
    print(kwargs)

f(a=1, b=2, c=3)


{'a': 1, 'b': 2, 'c': 3}

On peut bien entendu combiner positionnels et optionnels :


In [9]:
def f(*args, **kwargs):
    print(args, kwargs)

f(1, 2, 3, a=4, b=5, c=6)


(1, 2, 3) {'a': 4, 'b': 5, 'c': 6}

Le slicing

Le slicing (littéralement « découpage en tranches ») permet d’obtenir une sous-partie d’une séquence (c’est-à-dire une donnée de type str, bytes, list, tuple, etc) :


In [10]:
s = 'abcdef'
print(s[0:3])
print(s[:3])     # Équivalent à [0:3]
print(s[-3:-1])  # -1 signifie l’avant dernier caractère
print(s[-3:])    # Pas de fin = jusqu’à la fin
print(s[::2])    # Le troisième « argument », optionel, est le pas
print(s[:])      # Copie la séquence


abc
abc
de
def
ace
abcdef

Cela fonctionne de la même manière avec d’autres types de séquences :


In [11]:
l = [1, 2, 3, 4, 5, 6]
print(l[:4:2])


[1, 3]

Exercices

On va créer un système permettant de chiffrer et déchiffrer un message tapé par un utilisateur.

Ce système comprendra deux fonctions, cipher et decipher.

cipher va piocher un caractère sur deux et les coller, puis prendre les caractères restants, inverser leur ordre, et les ajouter à la suite. Pour chiffrer '0123456789', on prend donc '02468'. On prend les caractères restants, '13579', dont on inverse l’ordre pour obtenir '97531'. Et enfin on colle l’ensemble pour obtenir '0246897531'.

decipher va faire l’opération inverse pour récupérer le message original.

Exemple d’utilisation :


In [12]:
from math import ceil
from itertools import zip_longest

def cipher(s):
    return s[::2] + s[1::2][::-1]

def decipher(s):
    half = ceil(len(s) / 2)
    groups = zip_longest(s[:half], s[half:][::-1], fillvalue='')
    return ''.join(a + b for a, b in groups)

In [13]:
text = 'Python envoie du lourd'

ciphered = cipher(text)
print(ciphered)
print(decipher(ciphered))


Pto noed ordulu ivenhy
Python envoie du lourd

Exercice 14

Écrire la fonction cipher.

Elle doit être faite en deux lignes de moins de 80 caractères (y compris la déclaration de fonction). Aucune librairie n’est nécessaire.

Exercice 15

Écrire la fonction decipher.

Cette fonction est nettement plus difficile, et doit être faite en 4 à 8 lignes de moins de 80 caractères. Pour cette fonction. Cela peut être fait sans librairie externe, mais il est vivement conseillé d’utiliser les fonctions math.ceil et itertools.zip_longest.

Modules, paquets et librairies

Ce qu’on appelle un module est un fichier Python ou un dossier regroupant de nombreux fichiers Python.

On utilise le terme paquet pour désigner un dossier regroupant de nombreux fichiers Python.
Un paquet contient obligatoirement un fichier __init__.py, même vide. On utilise également ce terme pour désigner une librairie externe installable depuis le Python Package Index, que ce soit en réalité un paquet ou un module.

Le terme librairie est utilisé pour désigner tout module externe au projet dans lequel on travaille, généralement installée depuis PyPI.

Pour résumer :

  • module : fichier ou dossier Python local
  • paquet : dossier Python local ou librairie externe
  • librairie : module externe installé

⇒ C’est un peu le bazar, c’est vrai.

Exercice 16

Créer un fichier essai.py contenant la ligne truc = 3

Lancer la console Python dans le dossier contenant essai.py puis exécuter :

from essai import truc
print(truc)

3 doit donc être affiché !
De même, créer une fonction quelconque dans le fichier essai.py, importer et utiliser cette fonction.

Un paquet Python est un dossier contenant des fichiers Python.
Créer un dossier paquet_test contenant les fichiers __init__.py, a.py et b.py.

Dans a.py, créer une variable var_a et une fonction quelconque func_a.
Dans b.py, créer une variable var_b et une fonction quelconque func_b.
Dans __init__.py, écrire :

from .a import var_a, func_a  # Le . montre que l’import est relatif
from .b import var_b, func_b

Lancer la console Python dans le dossier contenant paquet_test, puis exécuter :

from paquet_test.a import var_a
from paquet_test import func_a, var_b

Utiliser les objets importés et constater que l’import fonctionne !

Les méthodes

Tout est objet en Python (ou presque).
Les méthodes sont des fonctions appliquées à un objet et correspondant à leur type. Exemples :

'BonJour'.lower()  # 'bonjour'
'27'.isdigit()     # True
3.7.is_integer()   # False
3.0.is_integer()   # True
x = 8 / 3
x.is_integer()     # False
une_liste = []
une_liste.append(1678)  # Ajoute 1678 à la liste

Exercice 17

Convertir une entrée utilisateur en minuscules puis en majuscules, et afficher les deux.

Exercice 18

Définir une liste vide, puis y ajouter 3 entrées utilisateur différentes converties en majuscules, puis afficher la liste.

Exercice 19

Définir une liste vide, puis y ajouter 3 nombres entiers saisis par l’utilisateur. On testera si l’entrée utilisateur est bien un nombre avant de l’ajouter à la liste. Afficher la liste.

Formatage de texte

Il existe deux principaux outils en Python pour formater des chaînes de caractères :

  • l’opérateur %, utilisant la même syntaxe qu’en C et nombre d’autres langages
  • la méthode .format(), plus facile et complet, mais moins rapide

Quelques exemples d’équivalents :

% .format() Résultat
'J’ai %s' % '28 ans' 'J’ai {}'.format('28 ans') J’ai 28 ans
'%d %s' % (28, 'ans') '{} {}'.format(28, 'ans') 28 ans
'%(age)d ans' % {'age': 28} '{age} ans'.format(age=28) 28 ans

pyformat.info récapitule parfaitement la différence et toutes les possibilités des deux outils, avec moult exemples.

Depuis Python 3.6, on peut utiliser les formatted strings ou f-strings pour utiliser .format() plus simplement :


In [14]:
age = 28
texte = 'ans'
print(f'{age} {texte}')


28 ans

Exercice 20

Écrire une fonction random_table effectuant ceci :

  • Piocher n chaînes de caractères aléatoires de 1 à 30 caractères alphabétiques maximum. Ces chaînes de caractères peuvent contenir des espaces.
  • Faire correspondre à chaque chaîne de caractères un nombre à virgule aléatoire entre -1000 et 1000.
  • Afficher le tout sous la forme d’un tableau où le texte est aligné à gauche, et les nombres alignés à droite, avec un chiffre après la virgule.

Exemple :


In [15]:
from random import choice, uniform, randint
from string import ascii_lowercase

CHARS = ascii_lowercase + ' '

def random_table(size):
    for i in range(size):
        s = ''.join([choice(CHARS)
                     for i in range(randint(1, 30))])
        print('%-30s | %7.1f' % (s.lstrip().capitalize(),
                                 uniform(-1000, 1000)))

In [16]:
random_table(10)


                               |  -661.1
Q                              |   672.8
How                            |  -427.6
Fdtmfkqulliuzbztsajfaafhwikka  |   901.7
Hecfvoydygzzggro               |   -90.8
Cvubovnfwcx                    |  -816.9
Cz lafbkiswhbzthvvqg           |  -999.9
Bpbnblrlbflqtd                 |   356.3
Yq yzbhaait                    |  -306.5
Ccszaf jkrbq                   |  -274.2

Gestion des exceptions

Des erreurs arrivent, cela ne doit pas être une fatalité !
Par exemple, la fonction int peut lever des exceptions ValueError et TypeError.

Lorsqu’une exception est levée, par défaut Python quitte le programme.
Pour éviter qu’une exception ne quitte le programme, on doit capturer l’exception :


In [17]:
try:
    i = int('je ne suis pas entier')
except ValueError:
    i = 0
except TypeError:
    i = -1
else:
    print('Cool, aucune erreur !')
print(i)


0

Dans try, on écrit les lignes de code à tenter d’exécuter.
En cas d’erreur, on se réfère au except correspondant à l’erreur (ici ValueError) et on exécute son code.
Si aucune erreur ne se produit, on exécute ce qu’il y a dans else.

Seul try et un except sont obligatoires, n’utiliser else et les except supplémentaires que si nécessaires.

Exercice 21

Demander un nombre entier (positif ou négatif) à l’utilisateur et continuer à demander en boucle ce nombre tant que l’utilisateur tape quelque chose n’étant pas un nombre entier.

Lever une exception

En Python, lorsque que quelque chose d’innatendu et grave se produit, on préfère lever une exception plutôt que de faire planter et arrêter le programme.

Pour cela, il faut utiliser le mot-clé raise et choisir le type d’exception parmi la liste des exceptions disponibles. Idéalement, on ajoute un message d’erreur, mais ce n’est pas obligatoire.

Par exemple :


In [18]:
message = input('Quel âge avez-vous ? ')
if not message.isdigit():
    raise TypeError('Vous devez taper un nombre')
if message == '0':
    raise NotImplementedError
print('Vous avez', message, 'ans')


Quel âge avez-vous ? 28
Vous avez 28 ans

Lecture et écriture de fichier

Pour lire un fichier, utiliser le mot-clé with, la fonction open et des méthodes de l’objet renvoyé :

with open('un_fichier.txt') as fichier:
    contenu = fichier.read()
    print(contenu)

Si le fichier n’existe pas, une exception FileNotFoundError est levée.
Utiliser un tryexcept pour gérer cette situation.

Par défaut, open ouvre les fichiers pour être lus, le mode d’ouverture 'r'.
Pour avoir un comportement différent, il faut changer le mode d’ouverture.
Ainsi, pour écrire dans un nouveau fichier ou écraser un fichier existant :

with open('un_fichier.txt', 'w') as fichier:
    fichier.write('Une première ligne\nEt une seconde')

Un mode spécial, 'a' (pour “append”), permet d’ajouter du contenu à la fin du fichier :

with open('un_fichier.txt', 'a') as fichier:
    fichier.write('Une troisième ligne')

Exercice 22

Uniquement en utilisant Python, faire ce qui suit :

  • Créer un fichier essai.txt contenant le texte « Je sais écrire :D ».
  • Lire le fichier et vérifier qu’il contient bien le texte d’origine.
  • Vider le contenu du fichier et vérifier qu’il est vide.
  • Écrire dans le fichier 3 lignes d’entrées utilisateur.
  • Lire le fichier et afficher son contenu
  • Ajouter dans le fichier une ligne « Bon, finies les doléances. »
  • Lire à nouveau le fichier et afficher son contenu.

Exécuter un programme externe

Exécuter un programme externe pour obtenir son code de retour :

import os
return_code = os.system('ls /home/')

⇒ Intérêt vite limité, et peu sécurisé.

Exécuter un programme externe pour obtenir sa sortie :

from subprocess import getstatusoutput
return_code, output = getstatusoutput('ls /home/')
print(return_code)
print(output)

⇒ Plus intéressant, mais peu sécurisé et toujours un peu limité.

Interagir complètement avec un programme externe :

from subprocess import Popen, PIPE
p = Popen(['ls', '/home/'], stdout=PIPE, stderr=PIPE)
p.wait()
print(p.returncode)
print(p.stdout.read().decode())

… et plein d’autres interactions.

Script en ligne de commande

Pour exécuter un script directement sans appeler Python :

  • Ajouter #!/usr/bin/env python3 en première ligne du fichier (le “shebang”)
  • Rendre le script exécutable en exécutant chmod +x script.py

Le module argparse inclus dans Python permet de créer des outils de qualité en ligne de commande :

from argparse import ArgumentParser

parser = ArgumentParser(description='Ce script lit ou écrit un fichier.')
parser.add_argument('chemin', help='Chemin vers le fichier')
parser.add_argument('--contenu', help='Contenu écrit dans le fichier')
args = parser.parse_args()
if args.contenu is None:
    with open(args.chemin) as fichier:
        print(fichier.read())
else:
    with open(args.chemin, 'w') as fichier:
        fichier.write(args.contenu)

Pour plus de détails, lire la documentation : docs.python.org/3/library/argparse

Créons un journal intime en ligne de commande !

Journal intime

Créer un outil en ligne de commande permettant d’écrire et lire un journal intime.

Écosystème Python

Problèmes d’encodage

Depuis Python 3 ⇒ presque plus de problèmes d’encodage !
Voir Python 2 vs 3 précédemment.
Mais parfois toujours besoin de conversions.

Encodage UTF-8 (norme universelle respectée par tous sauf Windows)

Conversion bytesstr :
b'abc'.decode() # Renvoie 'abc'
Conversion strbytes :
'abc'.encode() # Renvoie b'abc'

Autres encodages

Ici on utilise par exemple ISO 8859-1, la norme Windows pour l’Europe universelle. C’est sans doute l’encodage autre que UTF-8 que vous rencontrerez le plus souvent.

Conversion bytesstr :
'\xe9'.decode('iso-8859-1') # Renvoie 'é'
Conversion strbytes :
'é'.encode('iso-8859-1') # Renvoie b'\xe9'

Voir la liste complète des noms d’encodages gérés par Python.

Gérer les fichiers et dossiers avec pathlib

Dans des scripts, souvent besoin de lister des fichiers, découper des chemins etc.
⇒ pathlib, une solution récemment incluse à Python pour faciliter tout cela.

from pathlib import Path
usr_bin = Path('/usr/bin/')
print(usr_bin.is_dir())                 # True
print(list(usr_bin.glob('python*')))    # Liste les exécutables python
print(usr_bin.parent)                   # /usr
home = Path.home()
projets = home / 'Projets Python'
print(projets.exists())                 # Probablement False
print(Path('test').absolute())          # Transforme en chemin absolu.
print(Path('fichier.txt').name)         # fichier
print(Path('fichier.txt').suffix)       # .txt
print(Path('fichier.tar.gz').suffixes)  # ['.tar', '.gz']

Pour plus d’informations : docs.python.org/3/library/pathlib

Exercice 23

En utilisant exclusivement pathlib et argparse, écrire un script en ligne de commande nommé ls.py permettant d’afficher le chemin de tous les fichiers Python trouvés récursivement dans le dossier actuel. Cela affichera aussi optionnellement la taille des fichiers. Si un argument est passé à ls.py en ligne de commande, on cherche dans l’argument.

Les chemins seront affichés de manière absolue, et par ordre alphabétique. On limite le nombre de chemins affichés à 1000, modifiable par un argument.

Exemples :

./ls.py --limite 2
journal.py
ls.py
./ls.py /usr --limite 3 --afficher-taille
  7261 octets /usr/lib/update-notifier/apt_check.py
  4848 octets /usr/lib/update-notifier/backend_helper.py
 15467 octets /usr/lib/rhythmbox/plugins/alternative-toolbar/alttoolbar_plugins.py

(Fin de la parenthèse écosystème)

Les décorateurs

Un décorateur est une fonction modifiant une fonction, une méthode ou même une classe pour lui ajouter des fonctionnalités.


In [19]:
def espionner_fonction(fonction):
    def nouvelle_fonction(*args, **kwargs):
        print('Début de', fonction.__name__, 'arguments :', args, kwargs)
        resultat = fonction(*args, **kwargs)
        print('Fin de la fonction, résultat :', resultat)
        return resultat
    return nouvelle_fonction

@espionner_fonction
def calcul(x, y=0):
    return x ** 2 + y

In [20]:
calcul(2)


Début de calcul arguments : (2,) {}
Fin de la fonction, résultat : 4
Out[20]:
4

In [21]:
calcul(x=5, y=8)


Début de calcul arguments : () {'x': 5, 'y': 8}
Fin de la fonction, résultat : 33
Out[21]:
33

Les décorateurs — suite

Utile notamment pour :

  • Indiquer qu’une fonction doit être utiliser par un autre système (en programmation événementielle)
  • Transformer une simple fonction en quelque chose de complexe (une page web par exemple, avec Flask)

Programmation Orientée Objet

Ou POO

Bref historique

Les concepts de POO ont commencé à être formulés dans les années 1960, soit au tout début de la programmation.

Des premiers langages s’essayèrent à la POO dans les années 1960 et 1970.

Toutefois, c’est dans les années 1980 que se créent les premiers langages dont le concept d’objet est central, popularisé essentiellement par C++, la version étendue à la POO du célébrissime C.

Dans les années 1990, le concept d’objet est au cœur de tous les nouveaux langages : Python, Java, Ruby, C#, etc.

Dans ces langages des années 1990, presque tout est objet. C’est comme cela qu’en Python, nous avons pu utiliser des méthodes sur quasiment tous les types de données.

Java est un exemple célèbre de POO pour son utilisation abusive : tout programme Java définit des classes, même si c’est pour écrire le fameux « Hello World » !

En Python, nous n’avons pas encore défini de classe, car on peut très bien se passer d’en créer. Toutefois, c’est indispensable dans bien des cas.

La POO, pourquoi ?

La POO répond à un problème très concret : comprendre du code rapidement, travailler en équipe avec moins de stress, le tout en un minimum d’effort.

Le problème que résout la POO est de comprendre quelle donnée on manipule. Sans objets, on ne manipule que des listes, des dictionnaires, des entiers, du texte. Dès qu’un programme se complique, il est difficile de mémoriser qu’une variable qu’on a récupéré à l’entrée d’une fonction contient, par exemple, « une liste de dictionnaires associant du texte à une liste de nombres entiers ». C’est encore pire lorsqu’on manipule des structures récursives.

L’intérêt de la POO consiste à regrouper des données sous un nom parlant. En gros, cela revient à remplacer la description d’une variable voiture de ceci :

Des plaques de métal jointes avec des vis fixées
sur plusieurs barres de métal soudées entre elles
autorisant la rotation d’un axe joint à deux morceaux de métal
auxquels sont adjoints des membranes de caoutchouc sous pression.

à ceci :

Une carosserie sur un châssis muni d’un essieu entraînant les roues.

Ici, carosserie, châssis, essieu et roue sont des classes qu’il faut définir. Mais quand je manipule le concept « voiture », je me fiche de connaître le détail de conception de la carosserie, du châssis, etc. J’ai besoin de manipuler une quantité raisonnable et compréhensible de concepts clairs. Si je veux connaître le détail du fonctionnement d’un châssis, je vais voir sa classe.

La POO, quand ?

Il n’y a pas de règle claire sur le moment où arrêter d’utiliser des types tout faits et créer ses classes pour clarifier.

Plusieurs signes indiquent qu’il faut avoir recours à des classes :

  • si lors de la conception vous vous rendez compte que les données seront compliquées à architecturer et/ou manipuler
  • si vous avez déjà du code dans une ou plusieurs fonctions et que vous sentez que c’est très sale alors que vous avez essayé de coder correctement
  • si vous avez besoin de réutiliser un même ensemble de code, mais en le modifiant un petit peu à chaque réutilisation
  • si une heure après avoir tapé du code que vous compreniez pendant la frappe, vous ne pigez plus rien à ce que vous avez écrit

En résumé, quand ça risque d’être compliqué, structurer avec des classes.

Dans tous les autres cas, surtout pour des scripts, utiliser des fonctions ou même du code directement tapé à la racine du programme.

Les classes et objets

Une classe :

  • = un type, sauf qu’il est défini en Python et non en C
  • = un plan de fabrication d’objets

Un objet = une instance d’une classe ou d’un type

Quand on crée un objet à partir d’une classe, on dit qu’on instancie une classe.

Éléments importants d’une classe :

  • nom de la classe
  • attributs de classe
  • attributs d’instance
  • méthodes, dont les méthodes spéciales/magiques
  • propriétés
  • héritage

Définir une classe vs instancier un objet

Création d’une classe :


In [22]:
class Stylo:              # Nom de classe
    encre_par_lettre = 1  # Attribut de classe

Création d’un objet / instanciation de la classe :


In [23]:
stylo = Stylo()

Exercice 24

On va créer une classe permettant de générer du HTML.

Comme le HTML contient des règles spéciales pour chaque type de balise, on va créer au final plusieurs classes définissant les spécificités de chaque type de balise.

Pour le moment, on s’intéresse à la logique générale.

On crée donc une classe HtmlTag contenant :

  • Un attribut name valant 'div'. Dans chaque nouveau type, on changera ce nom pour être img, span, p, strong, etc.
  • Un attribut self_closing valant False. Cela permettra de définir si un élément HTML ressemble à <div></div> (la plupart des cas, quand self_closing est False) ou <div /> (quand self_closing est True).
  • Un attribut mandatory_attrs valant []. C’est une liste d’attributs obligatoires pour un type d’élément HTML donné.

Attribut de classe vs attribut d’instance

Modifier un attribut sur une instance le modifie uniquement pour cette instance :


In [24]:
print(Stylo.encre_par_lettre, stylo.encre_par_lettre)
stylo.encre_par_lettre = 2
print(Stylo.encre_par_lettre, stylo.encre_par_lettre)


1 1
1 2

Mais modifier un attribut de classe donne des conséquences plus complexes :


In [25]:
print(Stylo.encre_par_lettre, stylo.encre_par_lettre)
Stylo.encre_par_lettre = 2
print(Stylo.encre_par_lettre, stylo.encre_par_lettre)
stylo.encre_par_lettre = 3
print(Stylo.encre_par_lettre, stylo.encre_par_lettre)
Stylo.encre_par_lettre = 4
print(Stylo.encre_par_lettre, stylo.encre_par_lettre)


1 2
2 2
2 3
4 3

Pourquoi stylo.encre_par_lettre est il modifié la première fois, mais pas la 3e fois ? Parce que la première fois, l’attribut n’est pas encore défini sur l’instance, il récupère l’attribut de classe à la place.

Définir des méthodes

En Python, une méthode se définit exactement comme une fonction, à ceci près :

  • Une méthode est indentée.
  • Une méthode contient self en premier argument. Il s’agit de l’objet actuel, ce qui va permettre de le modifier, d’utiliser ses attributs ou ses méthodes

In [26]:
class Stylo:
    encre_par_lettre = 1

    def ecrire(self, texte):
        consommation = self.encre_par_lettre * len(texte)
        print("%s écrit '%s', consommant %d d’encre"
              % (self, texte, consommation))

stylo = Stylo()
stylo.ecrire('bonjour')


<__main__.Stylo object at 0x7fa51831e5c0> écrit 'bonjour', consommant 7 d’encre

Exercice 25

Améliorer HtmlTag pour lui ajouter une méthode render_html sans argument à part self.

Cette méthode effectuera le rendu HTML de l’élément, sous forme de chaîne de caractères.

Cela doit donc renvoyer

  • <div></div> si name vaut 'div' et self_closing vaut False
  • <img /> si name vaut 'img' et self_closing vaut True
  • […]

Les méthodes spéciales

« Spéciales » car elles ont des sens bien particuliers dans Python.
On les appelle aussi couramment « méthodes magiques ».

Elles permettent aux classes de s’intégrer dans la syntaxe Python très naturellement et ainsi d’éviter d’utiliser tout le temps des méthodes lors de l’utilisation d’une classe.

Toutes les méthodes spéciales commencent et finissent par un double underscore, __.

Quelques méthodes spéciales importantes :

Nom de la méthode Utilité
__init__ Instanciation de la classe
__str__ Conversion de l’objet en chaîne de caractères
__repr__ Représentation de l’objet
__add__, __sub__, … Opérations arithmétiques
__lt__, __gt__, __eq__, … Comparaisons
__del__ Suppression de l’objet (même automatique)

Pour en savoir plus, voir la liste complète.

Les méthodes spéciales — exemple


In [27]:
class Stylo:
    nom = 'stylo'
    encre_par_lettre = 1

    def __init__(self, couleur):
        self.couleur = couleur
        print('On crée un stylo %s.' % couleur)

    def __str__(self):
        return self.nom

    def ecrire(self, texte):
        consommation = self.encre_par_lettre * len(texte)
        print("%s écrit '%s', consommant %d d’encre."
              % (self, texte, consommation))

stylo = Stylo('bleu')
stylo.ecrire('bonjour')


On crée un stylo bleu.
stylo écrit 'bonjour', consommant 7 d’encre.

Exercice 26

Ajouter une méthode spéciale à HtmlTag pour que
str(HtmlTag()) renvoie
'<div></div>'.

Exercice 27

Ajouter une méthode spéciale à HtmlTag de sorte que :

  • On puisse préciser un nombre potentiellement illimité d’éléments contenus.
    Par exemple, le rendu de
    HtmlTag(HtmlTag(), HtmlTag()) serait
    <div><div></div><div></div></div>.
  • On puisse préciser les attributs HTML.
    Par exemple, le rendu de
    HtmlTag(id='container', data_url='http://python.org') serait
    <div id="container" data-url="http://python.org"></div>.

Pour ce faire, on devra aussi ajouter des attributs d’instance children et attrs.

En cas d’absence d’un attribut obligatoire, lever une KeyError avec un message adéquat.

Gérer le cas spécial de l’attribut class (mot réservé en Python) en attendant à la place un argument class_name.

Exercice 28

Faire en sorte qu’on puisse demander le nombre d’enfants d’un élément HTML avec la fonction len :

tag = HtmlTag(HtmlTag(), HtmlTag())
print(len(tag))  # Renvoie 2

Exercice 29

Faire en sorte que l’on puisse facilement lire, modifier ou retirer des attributs comme ceci :

tag = HtmlTag(data_url='')
tag['id'] = 'container'
print(tag['id'])  # Renvoie 'container'
del tag['id']

Exercice 30

Modifier HtmlTag pour qu’on puisse saisir du texte dans la liste des enfants, de sorte que
HtmlTag(HtmlTag(), 'du texte') renvoie
<div><div></div>du texte</div>

Les propriétés

Une propriété s’utilise comme un attribut, mais se définit comme une méthode, avec un décorateur. Cela permet de faire un attribut calculé dynamiquement et/ou d’ajouter un comportement logique au moment de la lecture ou écriture de la propriété.

Exemple de propriété en lecture seule :


In [28]:
import datetime

class Human:
    def __init__(self, birth_year):
        self.birth_year = birth_year

    @property
    def age(self):
        return datetime.date.today().year - self.birth_year

human = Human(1989)
print(human.age)
human.age = 60


29
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-28-8da6b4fa5d79> in <module>()
     11 human = Human(1989)
     12 print(human.age)
---> 13 human.age = 60

AttributeError: can't set attribute

Les propriétés — suite

Exemple de propriété en lecture et écriture :


In [29]:
import datetime

class Human:
    def __init__(self, birth_year):
        self.birth_year = birth_year

    @property
    def age(self):
        return datetime.date.today().year - self.birth_year

    @age.setter
    def age(self, new_age):
        self.birth_year = datetime.date.today().year - new_age

human = Human(1989)
print(human.age)
human.age = 60
print(human.age, human.birth_year)


29
60 1958

Public ou privé ?

Dans une classe, un attribut, méthode ou propriété peut-être public ou privé.

Ce qui est public est accessible à tout le monde, et ce qui est privé n’est accessible que depuis l’intérieur de la classe, dans une méthode ou une propriété.

Dans certains langages, il existe aussi d’autres subtilités pour un attribut/méthode/propriété, comme la lecture seule (ou readonly) ou le fait d’être statique.

En Python, on définit tout est public par défaut, sauf si le nom de l’attribut/méthode/propriété commence par deux underscores __ (uniquement au début, pas à la fin).

Public ou privé ? — Exemple


In [30]:
class Human:
    def __init__(self, birth_year):
        self.__birth_year = birth_year

    def get_century(self):
        if self.__birth_year <= 2000:
            return 'XXe'
        return 'XXIe'

human = Human(1989)
print(human.get_century())
print(human.__birth_year)


XXe
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-30-79b71285e101> in <module>()
     10 human = Human(1989)
     11 print(human.get_century())
---> 12 print(human.__birth_year)

AttributeError: 'Human' object has no attribute '__birth_year'

Entre public et privé

Par convention, il arrive très souvent en Python qu’on fasse commencer un attribut/méthode/propriété par un seul underscore, comme def _get_internal_stuff(self):. Ce n’est pas privé, mais cette convention indique que c’est une fonctionnalité interne qu’il est déconseillé d’utiliser ou modifier si on a un doute.

On utilise très rarement les attributs/méthodes/propriétés privés, car on considère que les développeurs sont adultes et responsables. On préfère autoriser un développeur à faire des bêtises et ne pas le bloquer plutôt que de le traiter comme un enfant turbulent. On préfère presque systématiquement le simple underscore.

Améliorons Stylo

class Stylo:
    encre_par_lettre = 1

    def __init__(self, couleur='noir'):
        self.couleur = couleur
        self.remplir()

    def remplir(self):
        self.__quantite_encre = 40

    @property
    def est_vide(self):
        return self.__quantite_encre <= 0

    def ecrire(self, texte):
        self.__quantite_encre -= len(texte) * self.encre_par_lettre
        if self.est_vide:
            raise ValueError('Le stylo est vide.')
        print(texte)

Maintenant on peut produire des stylos et s’en servir :

stylo = Stylo('rouge')      # Un stylo rouge
stylo.ecrire('quelque chose')
stylo.ecrire('blabla' * 5)  # Le stylo est vide.
stylo.remplir()             # Remet de l’encre
stylo.ecrire('blabla' * 5)  # Il marche à nouveau !

Héritage

Hériter d’une classe permet de réutiliser son code, tout en le personnalisant. Ici, on créé une sous-classe Crayon héritant de Stylo :

class Crayon(Stylo):
    encre_par_lettre = 0.5

crayon = Crayon()  # Un crayon noir
crayon.ecrire('quelque chose')
crayon.ecrire('blabla' * 5)
crayon.ecrire('blabla' * 5)
crayon.ecrire('blabla' * 5)  # Le stylo est vide.

On peut même écraser des méthodes, éventuellement en réutilisant leur fonctionnement de base.

class Feutre(Stylo):
    def remplir(self):
        self.quantite_encre = 60

    def ecrire(self, texte):
        try:
            super().ecrire(texte)  # Exécute la méthode écrasée
        except ValueError:
            raise ValueError('Le feutre est vide.')

Exercice 31

Créer une myriade de sous-classes à HtmlTag pour gérer des éléments HTML communs. Implémenter au moins les sous-classes :

  • P
  • Strong
  • Em
  • Span
  • Div
  • Img (attributs src et alt obligatoires, self-closing)

Créer ensuite des instances de ces sous-classes de sorte que le rendu HTML obtenu en utilisant str sur une des instances soit :

<div class="container">
  <p>
    <em>Python</em> est un
    <span title="Pas si compliqué">langage <strong>multi-paradigme</strong></span>.
    <img src="https://www.python.org/static/img/python-logo.png" alt="Python logo" />
  </p>
</div>

Attention, j’ai ajouté de l’indentation au HTML pour la lisibilité, votre code généré ne doit pas faire ces ajouts, il doit être en une ligne, un peu tout collé.

Exercice 32

Créer une classe Vehicule contenant :

  • un attribut de classe vitesse_max en km/h, valant la vitesse de la lumière
  • un attribut de classe consommation, la quantité de carburant consommée pour 100 km, valant 10
  • un attribut de classe reservoir_max définissant la taille de la réserve de carburant, valant 1000
  • un attribut d’instance reservoir, le remplissage du réservoir, plein par défaut
  • une méthode parcourir prenant en argument une distance en km et retirant de l’essence ou de l’électricité en accord avec consommation, et renvoyer la durée minimale du trajet en heures
  • une méthode faire_le_plein remplissant le réservoir

Empêcher le véhicule de rouler si il n’a plus assez d’essence pour parcourir la distance demandée.

Créer deux classes Voiture et Avion héritant de Vehicule, avec des vitesses maximales de 160 et 950, des consommations de 6 et 1500 et des réservoirs de 50 et 260000.

En utilisant ces classes, trouver approximativement en tâtonnant la distance maximale que peut faire une voiture et un avion, et en combien de temps minimum.

Créer deux classes C3 et Zoe2017 héritant de Voiture, avec des vitesses maximales de 150 et 155, des consommations de 6 et 13 et des réservoirs de 40 et 41. Trouver de la même manière à tâton les distances maximales et durées de voyages.

Exercice 33

Reprendre la classe Vehicule des exercices précédents, et y ajouter un attribut de classe nom valant 'Véhicule'.
Écraser cet attribut de classe dans toutes ses sous-classes, par exemple 'Avion' pour Avion ou 'Zoé 2017' pour Zoe2017.

Ajouter une méthode spéciale à Vehicule de sorte que str(Voiture()) affiche « Voiture », str(Zoe2017()) affiche « Zoé 2017 », etc.

Ajouter une méthode spéciale pour ajouter un peu de carburant à un véhicule comme ceci :

c3 = C3()
c3.parcourir(200)
print(c3.reservoir)  # 28.0
c3 += 5
print(c3.reservoir)  # 33.0

Ajouter une méthode spéciale qui affiche par exemple « C3 part à la casse ! » quand on supprime un objet. En Python, les objets se suppriment automatiquement, mais on peut forcer la suppression en faisant :

c3 = C3()
del c3

Héritage multiple

Python fait partie des rarissimes langages à suporter l’héritage multiple, c’est-à-dire qu’une classe peut être créée en héritant de plusieurs classes. Par exemple :


In [31]:
class Book:
    max_pages = 100000
    size = 'any'

class Novel(Book):
    max_pages = 2000
    size = 'pocket'

class ScienceReport(Book):
    max_pages = 500
    size = 'A4'

class ScienceNovel(ScienceReport, Novel):
    pass

book1 = Novel()
book2 = ScienceNovel()
print(isinstance(book1, Novel),
      isinstance(book1, Book),
      isinstance(book1, ScienceReport))
print(isinstance(book2, Novel),
      isinstance(book2, Book),
      isinstance(book2, ScienceReport))


True True False
True True True

Héritage multiple — mixin

La plupart du temps, on s’en sert pour intégrer des fonctions annexes mises ensemble dans une classe dite mixin. On met toujours les mixins avant la ou les classe(s) principale(s) :

class PlumeMixin:
    calligraphie = True

class StyloPlume(PlumeMixin, Stylo):
    pass

POO d’autres langages

La Programmation Orientée Objet existe dans la plupart des langages modernes. Les concepts vus restent les mêmes, mais la syntaxe change, naturellement.

Quelques concepts sont souvent absents :

  • méthodes spéciales
  • propriétés
  • héritage multiple

Pour une comparaison en détail, voir l’article Wikipedia comparant la POO) à travers les langages.

Référence en Python

class Stylo:
    encre_par_lettre = 1

    def __init__(self, couleur='noir'):
        self.couleur = couleur
        self.remplir()

    def remplir(self):
        self.__quantite_encre = 40

    @property
    def est_vide(self):
        return self.__quantite_encre <= 0

    def ecrire(self, texte):
        self.__quantite_encre -= len(texte) * self.encre_par_lettre
        if self.est_vide:
            raise ValueError('Le stylo est vide.')
        print(texte)

stylo = Stylo('rouge')      # Un stylo rouge
stylo.ecrire('quelque chose')
stylo.ecrire('blabla' * 5)  # Le stylo est vide.
stylo.remplir()             # Remet de l’encre
stylo.ecrire('blabla' * 5)  # Il marche à nouveau !

Équivalent en Java

public class Stylo {
    public int encreParLettre = 1;
    public String couleur = "noir";
    private int quantiteEncre;

    public Stylo() {
        this.remplir();
    }

    public Stylo(String couleur) {
        this.couleur = couleur;
        this.remplir();
    }

    public void remplir() {
        this.quantiteEncre = 40;
    }

    public boolean getEstVide() {
        return this.quantiteEncre <= 0;
    }

    public void ecrire(String texte) throws IllegalArgumentException {
        this.quantiteEncre -= texte.length() * this.encreParLettre;
        if (this.getEstVide()) {
            throw new IllegalArgumentException("Le stylo est vide.");
        }
        System.out.println(texte);
    }
}

public class UtiliseStylo {
    public static void main(String[] args) {
        Stylo stylo = new Stylo("rouge");
        stylo.ecrire("quelque chose");
        String texte = String.format("%0" + 5 + "d", 0).replace("0", "blabla");
        stylo.ecrire(texte);
        stylo.remplir();
        stylo.ecrire(texte);
    }
}

Équivalent en C++

#include <string>
#include <iostream>
#include <stdexcept>

using namespace std;

class Stylo {
    public:
        int encre_par_lettre = 1;
        string couleur;
        Stylo();
        Stylo(string couleur);
        void remplir();
        bool get_est_vide();
        void ecrire(string texte);
    private:
        int quantite_encre;
};

Stylo::Stylo() : couleur("noir") {
    this->remplir();
}

Stylo::Stylo(string couleur) : couleur(couleur) {
    this->remplir();
}

void Stylo::remplir() {
    this->quantite_encre = 40;
}

bool Stylo::get_est_vide() {
    return this->quantite_encre <= 0;
}

void Stylo::ecrire(string texte) {
    this->quantite_encre -= texte.length() * this->encre_par_lettre;
    if (this->get_est_vide()) {
        throw std::invalid_argument("Le stylo est vide.");
    }
    std::cout << texte << std::endl;
}

int main() {
    Stylo stylo = Stylo("rouge");
    stylo.ecrire("quelque chose");
    string texte = "";
    for (int i = 0; i < 5; i++) {
         texte += "blabla";
    }
    stylo.ecrire(texte);
    stylo.remplir();
    stylo.ecrire(texte);
}

(Fin de la POO)

Les expressions régulières

Une expression régulière permet de reconnaître un motif dans une chaîne de caractères et éventuellement d’extraire une partie de cette chaîne de caractéristiques.

Trouver si une chaîne correspond à un motif :

import re
match = re.match(r'^Mon nom est .+$', 'Mon nom est Bertrand Bordage')

Si match est None, alors le texte ne correspond pas au motif. Sinon, match est un objet permettant d’extraire des groupes de caractères. Pour définir des groupes :

import re
match = re.match(r'^Mon nom est (.+)$', 'Mon nom est Bertrand Bordage')
print(match.group(1))  # Affiche « Bertrand Bordage »

De nombreux moyens de préciser des motifs, tous listés sur la documentation du module re.

Notation Traduction Notation Traduction
. N’importe quel caractère ? Répète une expression 0 ou 1 fois
\w N’importe quel lettre * Répète une expression 0 ou plus de fois
\d N’importe quel chiffre + Répète une expression 1 ou plus de fois
[] N’importe lequel des caractères entre crochets {2,5} Répète une expression 2 à 5 fois

Exercice 34

On va écrire un script simulant une discussion banale.
À tout moment, l’utilisateur peut écrire quelque chose d’innatendu, le script ne doit pas planter.
Il doit afficher à la place « Désolé, je n’ai pas compris. » puis poser à nouveau sa question.

Écrire un script demandant à l’utilisateur « Nom et ville ? », l’utilisateur devra répondre par exemple :
« Je m’appelle Yves et j’habite Yvetôt » ou « Je m’appelle Antonio et j’habite Venise ».

Répondre à l’utilisateur :
« Bonjour Yves ! » ou « Bonjour Antonio ! »
Puis lui demander : « Quel temps fait-il à Yvetôt ? » ou « Quel temps fait-il à Venise ? »

L’utilisateur devra répondre par exemple :
« Nuageux, avec une température de 9 °C » ou « Ensoleillé, avec une température de 27 °C »

Récupérer la température, puis afficher :

  • « Brr… il caille ! :( » s’il fait moins de 0 °C
  • « Frisquet… » s’il fait moins de 10 °C
  • « Ça va, il fait bon :) » s’il fait plus de 20 °C
  • « Wow, il fait chaud ! :o » s’il fait plus de 30 °C

Afficher « Bon, je dois vous laisser, passez une bonne journée ! »

Écosystème Python

Analyser du HTML

Nous allons utiliser requests et BeautifulSoup 4 pour récupérer une page web et l’analyser. Installation :

pip install beautifulsoup4 requests

Ici, nous allons voir comment :

  • télécharger la page Twitter @CESI_alternance avec requests (documentation)
  • extraire les données numériques « Tweets », « Abonnements », « Abonnés » et « J’aime » à l’aide de BeautifulSoup 4 (documentation)

In [32]:
from bs4 import BeautifulSoup
import requests

account = 'cesi_alternance'
# Requête HTTP téléchargeant la page du compte Twitter
reponse = requests.get('https://twitter.com/' + account)
# Création d’un objet BeautifulSoup pour analyser le HTML
soup = BeautifulSoup(reponse.text, 'html.parser')
# On utilise un sélecteur CSS pour récupérer
# la balise contenant le nombre d’abonnés
followers_css_selector = '.ProfileNav-item--followers .ProfileNav-value'
followers_tag = soup.select(followers_css_selector)[0]
# Récupère la valeur de l’attribut HTML data-count de la balise
followers = followers_tag['data-count']

print('@' + account, 'a', followers, 'abonnés.')


@cesi_alternance a 823 abonnés.

Programmation asynchrone

La programmation asynchrone sert à exécuter plusieurs tâches simultanément.
Utile pour :

  • Des boucles lourdes mettant trop de temps à s’exécuter par rapport au temps souhaité (traitement de données, génération de niveaux d’un jeu vidéo, etc)
  • Exécuter certaines tâches où Python attend un événement extérieur (requête SQL ou HTTP, ouverture de milliers de fichiers, etc)

Dans Python, plusieurs modules pour la programmation asynchrone : threading, multiprocessing, concurrent Malheureusement, ils sont complexes à utiliser :

  • difficile de synchroniser les tâches
  • difficile à débugger

Récemment, une nouvelle manière de faire de la programmation asynchrone en Python : le module asyncio et les mots-clés async et await.

Révolutionnaires, mais cela manque de maturité, cela changera encore beaucoup à l’avenir et il y a encore de quelques bugs.

⇒ Il vaut mieux attendre encore un peu !

Faisons un Action RPG textuel !

Action RPG textuel

Créer un petit jeu avec des créatures affrontant un humain (un peu comme Pokémon par exemple). Les humains et créatures ont quelques capacités classiques des RPG :

  • niveau
  • vie
  • endurance

On gère trois actions pour commencer :

  • frapper pour retirer des points de vie à l’adversaire
  • attendre pour récupérer de la vie et de l’endurance
  • esquiver pour éviter le prochain coup de l’adversaire

Action RPG textuel — conseils de réalisation

Certaines données non affichées sont presque indispensables :

  • l’expérience (le changement de niveau se fait en accumulant de plus ou moins d’expérience par combat)
  • la vie maximale que peut contenir le personnage
  • la force (pour définir la puissance des coups, qui sera modifiée en fonction du niveau de l’ennemi, ainsi que le coût d’endurance par coup)
  • l’agilité (pour définir le coût d’endurance par esquive, plus l’agilité est grand, plus le coût est bas)

Il est très fortement recommandé de créer une classe, par exemple Personnage stockant les données et gérant la logique des personnages. Ainsi, chaque nouveau type de personnage sera une sous-classe ajustant les paramètres.

Pour les plus téméraires, vous pouvez ajouter de nombreux systèmes usuels, comme les coups critiques, la fuite d’un combat, l’armure, différents types d’attaque, etc.