Accueil

Traduction

Tutoriel Python - sommaire

Tutoriel Python - recherche

L'auteur : Patrick Darcheville

Vous pouvez me contacter via Facebook pour questions & suggestions : Page Facebook relative à mon site

Python : des programmes plus professionnels

Un programme structuré

Dès qu'un programme correspond à un algorithme un peu plus complexe (que ce que nous avons vu jusqu'à présent), il est préférable de le structure.

Le programme principal est relativement court car on se contente de saisir des variable, d'appeler des sous-programmes (SP).
Un même sous-programme peut être appelé plusieurs fois par le programme principal.

Mais avec Python cette distinction n'a pas d'intérêt pratique : les procédures et fonctions sont définies avec le même mot clé def.
Dans le code d'une fonction il y aura une instruction return ; instruction qui sera absente dans une procédure.

Premier programme structuré

Objectif :

Le script

# nom du programme : cercle_plus.py
#objet du programme : programme structuré sur le cercle

#procédures et fonctions 
def cercle1(r):
    c = rayon *2 * math.pi
    s = rayon**2 * math.pi
    return c, s

def cercle2(r):
    pencolor('navy')
    pensize(3)
    speed("slow")
    circle(r)
    exitonclick()
#------------------------------------------------
#programme principal
import math
from turtle import *
rayon = input("saisir le rayon (un entier) : ")
rayon =eval(rayon) #conversion chaine en numérique
circonference, aire = cercle1(rayon)    # appel fonction cercle1
print ("circonférence du cercle : " , circonference)
print ("aire du cercle : " , aire)
cercle2(rayon)  #appel procédure cercle2

Description de la fonction "cercle1"

J'utilise la fonction pi du module math.
Les variables définies dans cette fonction sont : r, c, s
Cette fonction retourne un tuple de deux valeurs.

Description de la procédure "cercle2"

La variable utilisée dans cette procédure est r.
Cette variable est utilisée pour dessiner le cercle avec le module turtle.

Description du programme principal

Importation de deux modules.
Saisie du rayon.
Appel de la fonction "cercle1" avec passage en argument du rayon.
Les deux valeurs retournées par la fonction "cercle1" sont récupérées dans le PP respectivement dans les variables "circonference & aire".
Affichage de ces deux variables.

Appel de la procédure "cercle2" avec passage en argument du rayon.

Batterie de tests

Vous avez saisi le code source et vous vous êtes assuré qu'il fonctionne correctement.
Au fait, n'oubliez pas de cliquer dans la fenêtre graphique pour "reprendre la main".

Maintenant vous allez découvrir via des tests, la notion de portée des variables ainsi que la problèmétique des erreurs de saisie.

Portée des variables

Premier test : dans les deux SP ("cercle1" & "cercle2") rajoutez l'instruction "print(rayon)" ; exécutez de nouveau le programme !
Pas de plantage : le rayon est affiché deux fois.

Deuxième test : dans le programme principal rajoutez " print(c,s)" puis lancez l'exécution.
Il y a plantage !

...
NameError: name 'c' is not defined

Tout cela est normal.
Une variable définie au niveau d'un SP (procédure ou fonction) n'est connue que de cette routine ; elle ne peut être manipulée dans le programme principal.
Par contre une variable définie au niveau du programme principal peut être manipulée également dans les SP ; Cette variable a donc une portée plus grande ; on dit qu'elle est globale.

Erreur de saisie

Lancez l'exécution du programme et saisissez la chaine "100 cm" . Il y a "plantage" !
La fonction eval() est incapable de convertir en numérique une chaine comprenant des chiffres mais aussi des lettres.

C'est la faute de l'utilisateur qui n'a pas lu l'invite de commande : un entier !
Mais un bon programme doit prévoir les étourderies de l'utilisateur. Il faut donc améliorer ce programme et prévoir un contrôle de saisie.
C'est l'occasion d'introduire une notion très puissante mais parfois un peu délicate : les expressions régulières.
On retrouve ce concept dans tous les langages.

Nouvelle écriture du programme principal

Il y a en effet pas de changement quant aux SP.

#programme principal
import math
from turtle import *
import re
rayon =""
gabarit ="^[0-9]{1,}$"
while not re.search(gabarit, rayon):
    rayon = input("saisir le rayon (un entier !) : ")
rayon =eval(rayon)      #conversion chaine en numérique
...

Whoua ! ça se complique sérieusement.
Voyons toutes les nouveautés :
import re : activation du module re (Regular Expressions) donc le module qui traite des expressions régulières
gabarit ="^[0-9]{1,}$" : la variable gabarit contient une expression régulière qui veut dire "au moins un chiffre".
while not re.search(gabarit, rayon) : tant que le contenu de rayon ne correspond pas au format de saisie défini dans gabarit il faut rester dans la boucle de saisie.
Nous avons donc utilisé la méthode search() du module re. et aussi la syntaxe while not … (tant que FAUX)

Les expressions régulières

Construire une expression régulière

Une expression régulière est une chaine qui commence par le caractère "^" et se termine pas "$" puis comprend 1 à N couples "classe – quantificateur".
Une classe est délimitée par des crochets, le quantificateur est délimité par des accolades.
Dans l'exemple précédent il n'y avait qu'un couple "classe-quantificateur".

Les classes

Une classe indique le ou les caractères autorisés à la saisie. On reconnait une classe car elle est entre crochets.
Une classe peut être un énumération de caractères autorisés ou un intervalle ou un mélange des deux.

Les quantificateurs

Un quantificateur suit une classe et indique le nombre de caractères autorisés à la saisie. Un quantificateur est entre accolades ou est un symbole :

Expressions régulières : exemples

nom_g = "^[A-z ']{2,}$"
prenom_g = "^[A-z ']{2,}$"
telephone_g = "^[0-9]{10}$"
motpasse_g = "^[A-z0-9 ]{8,}$"

Les expressions régulières nom_g & prenom_g autorisent seulement les lettres minuscules et majuscules mais non accentuée et au moins deux.
L'expression telephone_g autorise seulement 10 chiffres (ni plus ni moins).
L'expression motpasse_g autorise les chiffres et les lettres non accentuées et au moins 8 caractères.

Expression régulière : autre syntaxe

Nous ne sommes pas obligé d'utiliser les classes et les quantificateurs pour écrire une RE (expression régulière).
On peut utiliser des caractères ordinaires et éventuellement contrôler le nombre d'occurences pour certains caractères.
Par exemple si nous cherchons les mots commençant par "abc" nous pouvons écrire en guise de RE : "^abc"
Si nous cherchons les mots se terminant par "que" nous pouvons écrire en guise de RE : "que$"
Si nous cherchons les mots contenant "chat" nous pouvons écrire en guise de RE : "chat".

Contrôler le nombre d'occurences

On peut écrire en guise de RE : "^abc*" ou "^abc+" ou "^abc?" que l'on peut lire par "commence par "abc". Le symbole qui suit la lette "c" contrôle l'occurence de cette lettre.

Pour la RE "^abc*" la lettre "c" peut être absente ou présente 1 à n fois. Donc les chaines "ab, abc,abcc" respectent cette RE.
Pour l'expression régulière "âbc+" ; la chaine "ab" n'est plus valide puisqu'il faut au moins une lettre "c".
Pour l'expression régulière "abc?" ; la chaine "abcc" n'est plus valide puisqu'il ne peut y avoir plus d'une lettre "c".

Un petit programme sur les expressions régulières

Le code

# expression_reguliere.py
import re
gabarit ="chat"
while True:
    mot = input("saisir un mot  ou 999:")
    if mot =="999" :
        break
    if re.search(gabarit,mot):
        print(f" {mot} contient l'expression régulière  {gabarit}")
    else:
        print(f" {mot} ne contient pas l'expression régulière {gabarit}")

Exécution

saisir un mot  ou 999:achat
 achat contient l'expression  chat
saisir un mot  ou 999:chatte
 chatte contient l'expression  chat
saisir un mot  ou 999:char
 char ne contient pas l'expression chat
saisir un mot  ou 999:chaton
 chaton contient l'expression  chat
saisir un mot  ou 999:chapeau
 chapeau ne contient pas l'expression chat
saisir un mot  ou 999:999

Programme "cercle_plus.py" : autre solution

Comme le rayon doit être un entier (donc uniquement des chiffres) il y a une solution plus simple concernant le contrôle de saisie du rayon : c'est à dire sans recourir aux expressions régulières.
Ci-dessus extrait du PP amélioré :

...
rayon =""
while not rayon.isdigit():
    rayon = input("saisir le rayon (un entier !) : ")
rayon =eval(rayon) #conversion chaine en numérique
...

Attention la méthode .isdigit() est une méthode de chaine ; elle retourne True seulement si la chaine ne contien que des chiffres.

>>> var ="10.5"
>>> var.isdigit()
False
>>> var.isnumeric()
False
>>> var2 ="122"
>>> var2.isdigit()
True
>>> var2.isnumeric()
True

La méthode .isnumeric() est équivalente à .isdigit().

Le programme "cercle_ter.py"

Dans ce nouveau programme il doit être possible de saisir en guise de rayon un nombre décimal.
Si on ne veut pas, par paresse, utiliser une expression régulière il y a une astuce qui pour tester quand même avec .isdigit() ...

Le programme principal

Je ne reviens pas sur les SP qui sont identiques que dans "cercle_bis.py".

Un extrait du PP :

rayon =""
while not rayon.replace("." ,"").isdigit():
    rayon = input("saisir le rayon sous forme d'un entier ou décimal:")
rayon =eval(rayon)      #conversion chaine en numérique

La méthode .isdigit() est appliquée à une chaine débarassée des points grâce à .replace(). <
Admettez que c'est subtil, mais ça marche !

Tirage au sort de cinq cartes : version définitive

Je reviens une nouvelle fois sur le thème "tirage au sort de cinq cartes dans un jeu de 32".
Dans le chapitre 8, ce programme avait déjà été amélioré avec l'emploi d'un "set" interdisant les doublons.
Dans la version définitive, le programme est structuré avec la création d'un fonction "tirage_carte()" afin d'avoir un code beaucoup plus lisible et donc plus facile à maintenir.
Notez que cette fonction n'a pas de paramètre ; pas d'argument à passer lors de son appel.

Le code

# nom programme : tirage_carte_fonction
#objet : tirage au sort de 5 cartes.
""" Version améliorée :
les cartes tirées sont rangées dans un ensemble.
Ainsi il ne peut y avoir de doublons.
"""
# sous-programmes 
def tirage_carte():
        carte_e = choice (enseignes)
        carte_r = choice (rang)
        carte_tiree = str(carte_r) + " de " + carte_e
        return carte_tiree

#programme principal
from random import *
enseignes = ['pique', 'coeur', 'carreau', 'trèfle']
rang = ['As', 'Roi', 'Dame', 'Valet', 10, 9, 8 , 7]
reponse ="o"
while reponse in "Oo":
        mon_jeu =set()      # la main stockée dans un ensemble
        n = 0
        while n < 5:
                carte = tirage_carte()            #appel fonction 
                mon_jeu.add(carte)
                n = len(mon_jeu)
        print("Mon jeu : ")
        for carte in mon_jeu:
                print(carte, end =" - ")
        print("\n ----------------------------------------------")
        reponse =input("Encore un tirage o/n  ? : " ) 

Tant que n (nombre d'éléments du set "mon_jeu") est inférieur à 5, il faut boucler.
carte = tirage_carte() : la variable globale "carte" récupère la valeur retournée par la fonction.

Le shell

Mon jeu : 
As de coeur - Dame de trèfle - 7 de coeur - 9 de trèfle - Valet de coeur - 
 ----------------------------------------------
Encore un tirage o/n  ? : o
Mon jeu : 
10 de pique - 8 de pique - Dame de coeur - Roi de pique - 9 de coeur - 
 ----------------------------------------------
Encore un tirage o/n  ? : o
Mon jeu : 
As de coeur - 7 de pique - 9 de carreau - 8 de carreau - Valet de coeur - 
 ----------------------------------------------
Encore un tirage o/n  ? : n

Formater les sorties

Les chaines de format ou "f-strings" permettent d'afficher les sorties selon un format approprié. Il suffit à l'intérieur du print() d'un f majuscule ou minuscule juste après la parenthèse ouvrante.

Thématique : calcul du montant TTC à partir du montant HT

Solution basique

#nom du programme : tva.py
#objet : calcul tva et ttc à partir du ht

ht = input("saisir le montant HT: ")
ht =eval(ht)

print("TVA : ", ht * 0.055)
print("TTC : ", ht * 1.055)

Si vous saisissez 1000, ce programme retourne :
TVA : 55
TTC : 1055

Un programme amélioré

#nom du programme : tva_plus.py
#objet : calcul tva et ttc à partir du ht avec contrôles de saisie et en fonction du taux
ht =""
while not ht.isdigit():
    ht = input("saisir le montant HT sous forme d'un entier  ! ")
ht = eval(ht)

type ="F"
while type not in "NnRr":
    type = input("saisir R ou N : ")
if type in "Rr":
        taux = 0.055
if type in "Nn" :
        taux = 0.2

print(f"TVA : {ht * taux} €")
print(f"TTC : {ht * (1 + taux)} €")

Ce programme calcule la TVA et le TTC au taux réduit ou au taux normal. De plus il y a des contrôles de saisie : vous pouvez faire plusieurs tentatives pour la saisie du montant HT et du code_tva.

On associe aux instructions print des "f-strings". Les montants affichés sont libellés en € !
Notez bien la syntaxe à l'intérieur des parenthèses de la fonction print :
f"TVA : {ht * taux} €" : le f doit précèder la chaine entre guillemets. Dans la chaine il y peut y avoir une expression de calcul (entre accolades). Juste après l'accolade fermante rajoutez € !

Trace d'exécution du programme

saisir le montant HT sous forme d'un entier  ! 1000 €
saisir le montant HT sous forme d'un entier  ! 1000
saisir R ou N : réduit
saisir R ou N : normal
saisir R ou N : n
TVA : 200.0 €
TTC : 1200.0 €
>>> 

Constatez que malgré de nombreuses erreurs de saisie, il n'y a pas plantage du programme.
Pour préciser le taux réduit on peut saisir r ou R et pour le taux normal : n ou N.
L'affichage des sorties est correct.

Un esprit chagrin peut faire remarquer qu'il serait préférable que les montants affichés apparaissent avec deux décimales.

La solution définitive :

print(f"TVA : {ht * taux:.2f} €")
print(f"TTC : {ht * (1 + taux):.2f} €")

J'ai donc rajouté juste avant l'accolade fermante la chaine ":.2f". ":" pour indiquer le début d'un format et ".2f" qui signifie "toujours deux décimales".

Programmes et fichiers texte

Dans le cadre des jeux, un script est souvent associé a un fichier texte.
Le fichier .txt peut contenir, par exemple, le meilleur score (ou le dernier score) ainsi que la date correspondante.
Le script doit actualiser ce fichier texte : écrire en ajout ou en écrasement dans le fichier.
Le chapitre suivant évoque justement le traitement de fichiers texte en Python.