Une collection est un ensemble d'informations partageant la même codification : chaque entrée partage le même nombre de colonnes et, sur une colonne, toutes les valeurs comportent le même type d'information :
Collection
Descripteurs
Pokemon n°
Nom
Type
Points de vie
Enregistrement d'index 0
1
Bulbasaur
Grass
318
Enregistrement d'index 1
2
Ivysaur
Grass
405
Enregistrement d'index 2
3
Venusaur
Grass
525
Enregistrement d'index 3
3
VenusaurMega Venusaur
Grass
625
Chaque ligne comporte des informations sur une entité qu'on nomme un enregistrement.
Chaque colonne se nomme un descripteur et l'ensemble des noms des descripteurs se nomme l'en-tête.
Collection en Python
La plupart du temps, on utilise un simple tableau Python (type list) pour stocker une collection.
1
collec=[enr0,enr1,enr2]
Pour lire ou agir sur les entrées une par une, on a donc deux possibilités :
L'ensemble du tableau à double entrée : Table ou Collection
La première ligne : l'en-tête
Les autres lignes : enregistrements, objets
Les colonnes : descripteurs, attributs
Le contenu d'une case : une valeur
1.2 Implémenter les enregistrements en Python
Nous allons voir trois manières de stocker nos enregistrements dans notre collection.
Tableau de tableaux
La collection est de type list[list] : un tableau qui contient d'autres tableaux représentant les enregistrements.
On crée des tableaux dont l'indice (index en anglais) 0 est l'identifiant, l'indice 1 le nom ...
[1, 'Bulbasaur', 'Grass', 318]
On les rajoute au fur et à mesure à notre collection avec la méthode des tableaux : list.append().
1
2
3
4
5
6
7
8
9
10
pokemons=[]# Variable contenant la tablepokemons.append([1,'Bulbasaur','Grass',318])# Rajoute ce pokemon à la fin de la tablepokemons.append([2,'Ivysaur','Grass',405])# Rajoute ce pokemon à la fin de la tableforiinrange(len(pokemons)):# Pour chaque indice de ce tableau pokemonsprint(pokemons[i])# Affiche le pokemon numéro iforpokemoninpokemons:# Pour chaque pokemon de ce tableau pokemonsprint(pokemon)# Affiche ce pokemon
Le problème de cette structure ? On utilise des tableaux non homogènes. Cela fonctionne, en Python, car quelques soient les choses contenues, le tableau ne contient pas vraiment les contenus mais les références vers les contenus. Tout tableau Python est un tableau de références, donc homogènes.
Mais si vous utilisez un langage utilisant de vrais tableaux, ça ne fonctionnera pas. Que faire alors ?
Comment avoir un objet officiellement capable de gérer des enregistrements non homogènes ?
Tableau de tuples
La collection est de type list[tuple] : un tableau qui contient des tuples représentant les enregistrements.
Les seules différences sont donc la présence des parenthèses et le fait que les enregistrements sont donc immuables.
1
2
3
4
5
6
7
8
9
10
pokemons=[]# Variable contenant la tablepokemons.append((1,'Bulbasaur','Grass',318))# Rajoute ce pokemon à la fin de la tablepokemons.append((2,'Ivysaur','Grass',405))# Rajoute ce pokemon à la fin de la tableforiinrange(len(pokemons)):# Pour chaque indice de ce tableau pokemonsprint(pokemons[i])# Affiche le pokemon numéro iforpokemoninpokemons:# Pour chaque pokemon de ce tableau pokemonsprint(pokemon)# Affiche ce pokemon
Cette fois, on peut utiliser sans problème différents types de contenus selon les descripteurs. Mais les enregistrements sont immuables.
Par contre, le contenu est immuable et pour rechercher telle ou telle information, il faut connaitre le numéro de colonne de cette information. Comment obtenir un type à la fois muable et capable de localiser une information autrement qu'avec un numéro de colonne ?
Tableau de dictionnaires
On peut utiliser des enregistrements sous forme de n-uplets nommés partageant les mêmes descripteurs
En Python, on peut utiliser les dictionnaires pour faire office de n-uplets nommés.
On crée des dictionnaires avec des clés id, nom ...
1
2
3
4
5
6
7
8
9
10
pokemons=[]# Variable contenant la tablepokemons.append({'id':1,'nom':'Bulbasaur','type':'Grass','pv':318})pokemons.append({'id':2,'nom':'Ivysaur','type':'Grass','pv':405})foriinrange(len(pokemons)):# Pour chaque indice de ce tableau pokemonsprint(pokemons[i])# Affiche le pokemon numéro iforpokemoninpokemons:# Pour chaque pokemon de ce tableau pokemonsprint(pokemon)# Affiche ce pokemon
On notera que le fait que les enregistrements soient des dictionnaires ne change rien à la gestion de la table / collection : la variable pokemons est toujours un tableau. C'est juste que les éléments du tableau sont maintenant des dictionnaires.
Le numéro du Pokemon sert à faire le lien entre différentes formes d'un même Pokemon : en forme normal, en forme améliorée... C'est pour cela que certains numéros mènent au même Pokemon mais sous une forme différente.
01° Réaliser les tâches suivantes :
Utiliser le programme pour voir comment il fonctionne.
1
2
3
4
5
6
7
pokemons=[]# Variable contenant la tablepokemons.append((1,'Bulbasaur','Grass',318))# Rajoute ce pokemon à la fin de la tablepokemons.append((2,'Ivysaur','Grass',405))# Rajoute ce pokemon à la fin de la tableforiinrange(len(pokemons)):# Pour chaque indice de ce tableau pokemonsprint(pokemons[index])# Affiche le pokemon numéro i
A quoi voit-on que les enregistrements sont des tuples ?
Rajouter la ligne de code permettant d'obtenir l'affichage suivant : j'ai rajouté une nouvelle entrée.
pokemons=[]# Variable contenant la tablepokemons.append((1,'Bulbasaur','Grass',318))# Rajoute ce pokemon à la fin de la tablepokemons.append((2,'Ivysaur','Grass',405))# Rajoute ce pokemon à la fin de la tablepokemons.append((3,'Venusaur','Grass',525))# Rajoute ce pokemon à la fin de la table
Autre version de la boucle :
.
.
forpokemoninpokemons:# Pour chaque pokemon de ce tableau pokemonsprint(pokemon)# Affiche ce pokemon
02° Réaliser les tâches suivantes :
Utiliser le programme pour voir comment il fonctionne.
1
2
3
4
5
6
7
pokemons=[]# Variable contenant la tablepokemons.append({'id':1,'nom':'Bulbasaur','type':'Grass','pv':318})pokemons.append({'id':2,'nom':'Ivysaur','type':'Grass','pv':405})forpokemoninpokemons:# Pour chaque pokemon de ce tableau pokemonsprint(pokemon)# Affiche ce pokemon
A quoi voit-on que les enregistrements sont des dictionnaires ?
Rajouter la ligne de code permettant d'obtenir l'affichage suivant : j'ai rajouté une nouvelle entrée.
A la présence d'accolades plutôt que de crochets (tableaux) ou parenthèses (tuples)
Rajout :
1
2
3
4
5
pokemons=[]# Variable contenant la tablepokemons.append({'id':1,'nom':'Bulbasaur','type':'Grass','pv':318})pokemons.append({'id':2,'nom':'Ivysaur','type':'Grass','pv':405})pokemons.append({'id':3,'nom':'Venusaur','type':'Grass','pv':525})
Boucle :
.
.
foriinrange(len(pokemons)):# Pour chaque indice de ce tableau pokemonsprint(pokemons[index])# Affiche le pokemon numéro i
C'est un moyen simple de transmettre des données : c'est un simple fichier-texte compréhensible par n'importe quel langage de programmation et n'importe quel tableur.
CSV veut dire Comma Separated Values : des valeurs séparées par des virgules.
Un tableur est capable d'ouvrir les fichiers CSV.
Structure des données dans le fichier CSV
(option) La première ligne du fichier contient les intitulés des attributs : l'en-tête.
Chaque ligne constitue un enregistement. Rappel : la fin de ligne correspond à un octet valant 10 (norme ASCII), représentée ci-dessous par ↲, et par le string "\n" en Python.
Chaque valeur est séparée des autres valeurs de l'enregistrement par une virgule.
Exemple
A titre d'exemple voici la représentation CSV d'un tableau contenant la collection ci-dessous :
Vous allez récupérer le fichier CSV contenant les Pokemons. Trois façons d'y aboutir :
Utiliser le module urllib.request ou
Utiliser le module requests ou
Télécharger directement le fichier depuis le dépot.
Nous allons ici simplement utiliser un programme qui va aller télécharger les données et créer le fichier CSV dans le même dossier que celui du programme Python que vous allez créer.
03 (solution A)° Téléchargement avec le module urllib. Réaliser les tâches suivantes :
Sauvegarder ce programme dans un répertoire qu'on nommera tables en le nommant download_csv.py.
Ouvrir le répertoire pour vérifier qu'il ne contient que votre script python pour le moment.
Lancer le programme et vérifier qu'il a téléchargé le fichier CSV qui devrait se trouver dans le même répertoire que votre fichier Python.
03 (solution B)° Téléchargement avec le module Requests. Réaliser les tâches suivantes :
(optionnel) Si vous êtes chez vous, dans Thonny, aller dans le menu TOOLS - MANAGE PACKAGES. Lancer une recherche sur Requests et installer le module requests.
Enregistrer le programme ci-dessous sous le nom download_csv2.py dans le répertoire tables.
Vous devrirez constater l'existence d'un fichier CSV nommé pokemon.csv dans ce même dossier.
Vérifiez que le fichier CSV mesdonnees2.csv a bien été créé automatiquement.
03 (solution C)° Si les deux scripts de téléchargement automatique ne fonctionne pas depuis votre poste, ce n'est pas grave, on peut le télécharger à la main.
Allez à l'adresse Github suivante (ou faire une simple recherche dans un moteur de recherche sur CSV + Pokemon): CSV POKEMON GITHUB.
Vous devriez aboutir sur une page qui vous présente visuellement le fichier sous la forme d'un tableau, comme un tableur.
Remarque : si le dépot git a disparu, vous pouvez utiliser cette version locale stockée sur infoforall : LE FICHIER CSV EN LOCAL. L'intérêt des dépôts en ligne est que les mises à jour sont automatiquement effectuées lorsqu'on réalise une modification pour corriger une erreur.
Appuyez sur le bouton RAW ("brut" en Français) : vous devriez voir le vrai contenu de ce fichier : un beau fichier CSV, à savoir un fichier-texte qui possède juste un nom qui finit en .csv.
Enregistrer le fichier sur votre disque local en utilisant le bouton de téléchargement et placez le dans votre répertoire tables.
Vérifier que le fichier CSV apparaît bien dans votre répertoire.
04° Quelque soit la méthode utilisée, ouvrir ce fichier avec votre tableur habituel.
Avec OpenCalc de LibreOffice, on obtient la page d'accueil suivante :
Il faut lui indiquer l'encodage utilisé (à priori UTF-8 si le fichier est récent) ainsi que le caractère séparateur. Ici la virgule.
Ensuite, on lance et on obtient ceci :
Maintenant que nous avons récupéré les données, nous allons créer la table correspondante. J'ai choisi d'utiliser la méthode du tableau de p-uplets nommés pour cette activité, mais le tableau de tableaux ou le tableau de tuples fonctionnent tout aussi bien.
05° Enregistrer le programme ci-dessous sous le nom creation_collec.py dans le même dossier que les autres.
Il s'agit de fonctions (décrites et construites dans l'activité Python nommée OpenData) qui permettent de transformer les données d'un fichier CSV en list[dict]. Inutile de les lire, nous allons juste les utiliser.
defdecomposer(chaine,separateur):"""Renvoie un tableau avec un élément de la chaine par case :: chaine (str) :: la chaîne de caractères à décomposer :: separateur (str) :: le caractère de séparation :: return (list[str]) :: un tableau contenant les différents éléments :: exemple :: >>> decomposer('1,voiture,sport,rouge', ',') ['1', 'voiture', 'sport', 'rouge'] """chaine=chaine.replace('\n','')# supprime le passage à la lignechaine=chaine.replace('\r','')# supprime le retour chariottableau=chaine.split(separateur)# tableau à partir de la chainereturntableaudefcreer_enregistrement(valeurs,attributs):"""Renvoie un dictionnaire utilisant attributs--entree comme clés--valeurs :: param valeurs(list) :: tableau des X futures valeurs :: param attributs(list) :: tableau des X futures clés :: return (dict) :: le dictionnaire-enregistrement voulu :: exemple :: >>> creer([0, 'voiture', 'sport', 'rouge'], ['id', 'type', 'utilisation', 'couleur']) {'id': 0, 'type': 'voiture', 'utilisation': 'sport', 'couleur': 'rouge'} """d={}# dico vide pour le momentiflen(valeurs)==len(attributs):# si les paramètres sont compatiblesforiinrange(len(valeurs)):# pour chaque indice des deux tableauxd[attributs[i]]=valeurs[i]# utilise attribut n°i comme clé de la valeur n°ireturnddefcreer_collection(nom_fichier):"""Renvoie un tableau de dictionnaires à partir d'un fichier CSV :: param nom_fichier(str) :: le nom du fichier CSV correct à traiter :: return (list[dict]) :: le tableau de dictionnaires """# 1 - On crée une tableau videcollec=[]# 2 - On ouvre le fichier en lecturefichier=open(nom_fichier,'r',encoding='utf-8')# 3 - On lit la première ligne pour créer le tableau des attributsentete=fichier.readline()# on lit la première ligne, \n inclusattributs=decomposer(entete,',')# extraction des attributs dans un tableau# 4 - On lit la suite du fichier ligne par ligneforligneinfichier:# pour chaque ligne du fichier-textevaleurs=decomposer(ligne,',')# extraction des valeurs dans un tableaunouveau=creer_enregistrement(valeurs,attributs)collec.append(nouveau)# rajout de l'enregistrement à la collection# 5 - On ferme le fichierfichier.close()# 6 - On renvoie la collectionreturncollec
06° Créons maintenant notre programme qui va utiliser le fichier creation_collection.py comme un simple module :
créer un fichier Python gestion_tables.py contenant le programme ci-dessous, en le plaçant toujours dans le même répertoire que les fichiers précédents.
Mettre en concordance le nom de votre fichier CSV et le nom du fichier précisé sur la ligne 3.
Lancer l'exécution du fichier.
Vous devriez constater si l'onglet VARIABLE de Thonny est ouvert (Menu View-Variables) que la variable pokemons contient bien les enregistrements lus à partir du fichier CSV.
Tapez ceci dans la console, pour vérifier visuellement qu'on obtient bien un tableau de dictionnaires :
>>> pokemons
???>>> for p in pokemons: print(p)↲↲???
Importation
Nous venons de voir comment on peut importer des fonctions contenues dans d'autres fichiers. Très pratique.
Les fameux modules math, turtle... ne sont rien d'autres que des fichiers nommées math.py, turtle.py...
C'est pour cela qu'il ne faut JAMAIS nommer l'un de vos fichiers Python en utilisant le nom d'un module existant ! Sinon, vous allez rendre invisible le module en cachant son nom derrière votre propre fichier.
On voit bien que la valeur associée à la clé 'Attack' n'est pas un entier pour le moment mais un string. C'est normal, nous avons construit nos enregistrements à partir d'un fichier-texte.
07-B° Très compliqué : associer le bon type à chacune des évaluations a, b, et c ci-dessous :
>>> a = pokemons
>>> b = pokemons[0]
>>> c = pokemons[0]['Attack']
Propositions :
Un tableau
Un dictionnaire
La valeur associée à l'une des clés d'un dictionnaire
Première chose à faire : transformer l'attaque et la défense en nombres.
✎ 08-A (sur un entier)° Taper et analyser les instructions suivantes :
>>> x = "5"
>>> x
'5'>>> x = int(x)
>>> x
5
✎ 08-B (sur un pokemon)° Taper et analyser les instructions suivantes :
On voit donc que pokemons[0]['Attack'] = int(pokemons[0]['Attack']) veut dire :
... = int(pokemons[0]['Attack']) : évalue sous forme d'un entier la valeur associée à la clé Attack puis
pokemons[0]['Attack'] = ... : place cet entier en tant que nouvelle valeur associée à la clé Attack
✎ 08-C (sur tous les pokemons)° Taper et analyser les instructions suivantes :
>>> for pokemon in pokemons : pokemon['Attack'] = int(pokemon['Attack'])↲↲>>> pokemons[10]['Attack']
???
La variable pokemons (avec un s) est la collection/table contenant les enregistrements/dictionnaires.
On constate que tous les pokemons ont maintenant des attaques sous forme d'un entier.
Tests de cohérence
Un test de cohérence consiste à vérifier que chaque descripteur contient bien une donnée ayant le bon type et vérifiant éventuellement certaines conditions supplémentaires.
On pourrait ainsi vérifier :
Que tous les enregistrements possèdent bien une attaque sous forme d'entiers et que la valeur soit positive dans tous les cas;
Que tous les enregistrements possèdent bien un type parmis une liste de valeurs autorisées : 'Grass', 'Fire', 'Water', 'Bug', 'Normal', 'Poison', 'Electric', 'Ground', 'Fairy', 'Fighting', 'Psychic', 'Rock', 'Ice', 'Dragon', 'Dark', 'Flying', 'Ghost' ou 'Steel'.
Hors programme : mais bien utile ici, try ... except ...
Nous avons déjà la fonction creer_collection() qui transforme le fichier CSV en list[dict] dans Python. Nous voudrions maintenant écarter les enregistrements non cohérents par rapport aux types attendus.
Pour cela, nous allons réaliser une fonction coherence_type() dont voici le prototype :
coherence_type(table:list[dict]) -> list[dict]
Cette fonction :
récupère une collection/table de type list[dict]
elle tente de convertir les strings dans le type attendu. Si tout se passe bien, on rajoute le dictionnaire modifié dans la nouvelle réponse.
si l'une des conversions se passe mal, on lève une exeption mais le mot-clé except permet de récupérer la main et faire autre chose que stopper et afficher un message rouge : on va afficher l'enregistrement problématique dans la console.
defcoherence_type(table):"""Vérifie et transforme si possible les valeurs des enregistrements dans le bon type :: param table (list[dict]) :: une table contenant des dictionnaires :: return (list[dict]) :: table avec uniquement les enregistrements valides """nouvelle=[]fordataintable:# Pour chaque enreg. dans la tabletry:data['Attack']=int(data['Attack'])data['Defense']=int(data['Defense'])data['Total']=int(data['Total'])data['HP']=int(data['HP'])data['Name']=str(data['Name'])ifdata['Name']!='':nouvelle.append(data)except:print(data)returnnouvelle
09° La fonction coherence_type() renvoie une nouvelle table, nommée nouvelle dans la fonction et rajoute l'enregistrement en ligne 25 s'il est valide.
Question :Comment se nomme la façon de créer la table avec append() dans la fonction coherence_type() ?
A : Par extension
B : Par extensions succesives
C : Par compréhension
D : Par appréhension
...CORRECTION...
Il s'agit d'une création par EXTENSIONS SUCCESSIVES(réponse B)
L'extension (réponse A) consiste à fournir directement le contenu dans les crochets : t = [5,10,15] par exemple.
Hors programme : mais bien utile ici, le formatage en longueur de caractéres des fStrings
voir_enregistrement() vous permet de visualiser avec un fString trois des descripteurs en fournissant 25 caractères d'espace pour le nom, 5 caractères pour l'attaque et 5 caractères pour la défense.
5
6
7
defvoir_enregistrement(data:dict)->None:"""Visualisation de trois descripteurs sur l'enregistrement"""print(f"{data['Name']:25}{data['Attack']:5}{data['Defense']:5}")
10° Mettre cette nouvelle version de gestion_tables.py en mémoire. Lancer puis Utiliser les instructions-console fournies sous le programme.
# 1 -Importation depuis des modulesfromcreation_collecimportcreer_collection# 2 - Déclaration des fonctionsdefvoir_enregistrement(data:dict)->None:"""Visualisation de trois descripteurs sur l'enregistrement"""print(f"{data['Name']:25}{data['Attack']:5}{data['Defense']:5}")defcoherence_type(table):"""Vérifie et transforme si possible les valeurs des enregistrements dans le bon type :: param table (list[dict]) :: une table contenant des dictionnaires :: return (list[dict]) :: table avec uniquement les enregistrements valides """nouvelle=[]fordataintable:# Pour chaque enreg. dans la tabletry:data['Attack']=int(data['Attack'])data['Defense']=int(data['Defense'])data['Total']=int(data['Total'])data['HP']=int(data['HP'])data['Name']=str(data['Name'])ifdata['Name']!='':nouvelle.append(data)except:print(data)returnnouvelle# 3 - Programmeif__name__=='__main__':pokemons=creer_collection('mesdonnees.csv')pokemons=coherence_type(pokemons)
Au lancement, aucun enregistrement rejété n'apparait dans la console normalement : le fichier CSV est cohérent.
Lorsqu'on visualise l'un des enregistrements, on voit que les clés Total, HP, Attack, Defense sont bien associées à des entiers.
✎ 11° Finir la fonction coherence_type() : il faut tenter de gérer les clés #, Sp. Atk, Sp. Def, Speed et Generation en entier (fonction int). Faire de même avec la clé Legendary qu'on transformera en booléen en testant data['Legendary'] == 'True'.
Après réalisation, voilà la réponse que vous devriez obtenir avec les instructions précédentes :
Pour cela, il suffit d'utiliser les fonctions vues dans la partie Algorithmique.
La seule différence, c'est que le tableau contient des dictionnaires et pas directement des nombres.
Point à totalement comprendre !
Dans les fonctions créées, il va donc falloir transformer
t[i] en
t[i][cle] puisque l'élément t[i] du tableau est un dictionnaire
1
2
3
4
5
6
deftrouver_max(t:list[dict],cle:str) ->'le plus grand trouvé':maxi=t[0][cle]foriinrange(1,len(t)):ift[i][cle]>maxi:maxi=t[i][cle]returnmaxi
12° Utiliser la fonction trouver_max() telle que notée ligne **52** : en fournissant la table et la clé "Attack" de façon à stocker la plus grande attaque qu'on puisse trouver sur les polemons. Regarder l'onglet Variables de Thonny pour visualiser cette valeur.
# 1 -Importation depuis des modulesfromcreation_collecimportcreer_collection# 2 - Déclaration des fonctionsdefvoir_enregistrement(data:dict)->None:"""Visualisation de trois descripteurs sur l'enregistrement"""print(f"{data['Name']:25}{data['Attack']:5}{data['Defense']:5}")defcoherence_type(table):"""Vérifie et transforme si possible les valeurs des enregistrements dans le bon type :: param table (list[dict]) :: une table contenant des dictionnaires :: return (list[dict]) :: table avec uniquement les enregistrements valides """nouvelle=[]fordataintable:# Pour chaque enreg. dans la tabletry:data['#']=int(data['#'])data['Name']=str(data['Name'])data['Type 1']=str(data['Type 1'])data['Type 2']=str(data['Type 2'])data['Total']=int(data['Total'])data['HP']=int(data['HP'])data['Attack']=int(data['Attack'])data['Defense']=int(data['Defense'])data['Sp. Atk']=int(data['Sp. Atk'])data['Sp. Def']=int(data['Sp. Def'])data['Speed']=int(data['Speed'])data['Generation']=int(data['Generation'])data['Legendary']=data['Legendary']=='True'ifdata['Name']!='':nouvelle.append(data)except:print(data)returnnouvelledeftrouver_max(collec,cle):"""Renvoie la valeur maximale lue dans la clé descripteur des dict. du tableau :: param collec(list[dict]) :: le tableau NON VIDE d'éléments qu'on peut comparer :: param cle(type valide) :: une clé valide pour du descripteur voulu :: return (type variable) :: la plus grande valeur dans ce descripteur """maxi=collec[0][cle]foriinrange(1,len(collec)):ifcollec[i][cle]>maxi:maxi=collec[i][cle]returnmaxi# 3 - Programmeif__name__=='__main__':pokemons=creer_collection('mesdonnees.csv')pokemons=coherence_type(pokemons)atk_max=trouver_max(pokemons,'Attack')
✎ 13° Récupérer maintenant également la valeur maximale de la défense. On placera le résultat dans une variable dfc_max
Nous n'avons plus qu'à créer un tableau en compréhension en fonction de certaines conditions.
Un premier exemple : trouver les meilleurs Pokemons en attaque. Par exemple, ceux qui ont au moins 80% de l'attaque maximale.
Et ça, c'est super simple !
14° Vérifier que les variables pokemons, atk_max et dfc_max soient bien en mémoire. Utiliser ensuite les instructions-console pour vérifier que cela fonctionne et demander des explications si vous ne comprennez pas telle ou telle indiciation ou résultat.
Si vous n'avez pas trouvé la question 13, rajouter simplement ceci à la fin du code : dfc_max = 230.
Si on tape ceci, cela veut dire qu'on récupère tous les pokemons de la table pokemon. On crée donc une copie de la collection.
>>> reponses = [pokemon for pokemon in pokemons]↲↲
Comment ne récupérer que ceux qui ont au moins 80% de l'attaque maximale ?
>
>>> reponses = [p for p in pokemons if p['Attack'] > (0.8 * atk_max) ]↲↲
Cette fois, on a rajouté une condition.
Demander d'afficher les résultats de deux manières :
>>> for pokemon in reponses : print(pokemon)↲↲>>> for pokemon in reponses : voir_enregistrement(pokemon)↲↲
Le plus beau avec la création par compréhension, c'est qu'on peut mettre plusieurs conditions liées par des or ou des and, des not ...
15° Tester ce nouveau programme qui comporte simplement une requête un peu plus avancée en dernière ligne. On voit qu'on cherche les pokemons dont l'attaque est au moins 80% du maximum disponible et (and) la défense 60% du maximum disponible.
# 1 -Importation depuis des modulesfromcreation_collecimportcreer_collection# 2 - Déclaration des fonctionsdefvoir_enregistrement(data:dict)->None:"""Visualisation de trois descripteurs sur l'enregistrement"""print(f"{data['Name']:25}{data['Attack']:5}{data['Defense']:5}")defcoherence_type(table):"""Vérifie et transforme si possible les valeurs des enregistrements dans le bon type :: param table (list[dict]) :: une table contenant des dictionnaires :: return (list[dict]) :: table avec uniquement les enregistrements valides """nouvelle=[]fordataintable:# Pour chaque enreg. dans la tabletry:data['#']=int(data['#'])data['Name']=str(data['Name'])data['Type 1']=str(data['Type 1'])data['Type 2']=str(data['Type 2'])data['Total']=int(data['Total'])data['HP']=int(data['HP'])data['Attack']=int(data['Attack'])data['Defense']=int(data['Defense'])data['Sp. Atk']=int(data['Sp. Atk'])data['Sp. Def']=int(data['Sp. Def'])data['Speed']=int(data['Speed'])data['Generation']=int(data['Generation'])data['Legendary']=data['Legendary']=='True'ifdata['Name']!='':nouvelle.append(data)except:print(data)returnnouvelledeftrouver_max(collec,cle):"""Renvoie la valeur maximale lue dans la clé descripteur des dict. du tableau :: param collec(list[dict]) :: le tableau NON VIDE d'éléments qu'on peut comparer :: param cle(type valide) :: une clé valide pour du descripteur voulu :: return (type variable) :: la plus grande valeur dans ce descripteur """maxi=collec[0][cle]foriinrange(1,len(collec)):ifcollec[i][cle]>maxi:maxi=collec[i][cle]returnmaxi# 3 - Programmeif__name__=='__main__':pokemons=creer_collection('mesdonnees.csv')pokemons=coherence_type(pokemons)atk_max=trouver_max(pokemons,'Attack')dfc_max=trouver_max(pokemons,'Defense')r=[pforpinpokemonsifp['Attack']>(0.8*atk_max)andp['Defense']>(0.6*dfc_max)]
Si on cherche à lancer le programme et à afficher le résultat :
>>> for pokemon in r : voir_enregistrement(pokemon)
TyranitarMega Tyranitar 164 150
GroudonPrimal Groudon 180 160
On remarquera qu'on aurait pu faire les requêtes l'une après l'autre également puisque c'est un et : d'abord FILTRER les meilleurs attaquants parmi tous les pokemons puis FILTRER les meilleurs défenseurs parmi les meilleurs attaquants.
✎ 16-A° Fournir les requêtes à faire pour obtenir la liste des pokemons ayant plus de 180 en attaque OU 180 en défense.
A partir de là, vous voyez qu'on peut s'amuser assez facilement en créant des filtres faisant exactement ce qu'on veut.
✎ 16-B° Fournir les requêtes à faire pour obtenir la liste des pokemons de génération 2 ET légendaires.
Le filtrage et la sélection sont donc des choses assez faciles à réaliser une fois qu'on a bien construit notre programme Python d'extraction. Comme ce sont des tâches usuelles dans tous systèmes d'informations, vous verrez comment utiliser les bases de données en Terminale NSI avec le langage SQL qui va nous simplifier le travail. Enormémemt.
Les doublons sont l'un des plus grands problèmes du stockage de données : des données strictement identiques présentes plusieurs fois. On veille donc à contrôler les rajouts sur les enregistrements : ce nouveau client est peut-être juste un client qui a oublié son mot de passe et se crée un nouveau compte !
6.1 Contrainte d'UNICITE
C'est pour cela qu'on rajoute toujours un attribut supplémentaire dans nos tables : un identifiant qu'on veut absolument n'attribuer qu'à un seul et unique enregistrement.
On la nomme souvent clé primaire : c'est une valeur qui doit :
Exister : on ne doit pas lui donner une valeur vide
Etre unique : elle ne doit apparaître que sur l'une des lignes, jamais deux fois.
Pour imopser cette contrainte d'UNICITE de la clé primaire, on vérifie que l'enregistrement n'existe pas déjà dans la collection puis on lui attribue une valeur de clé primaire qui n'existe pas déjà.
Dans le cas de notre collection de Pokemons, il n'y en a pas pour l'instant : la clé "#" fait le lien entre plusieurs formes d'un même Pokemon et plusieurs enregistrements portent donc ce même numéro.
✎ 17° Puisqu'on compare le nouveau enregistrement à tous ceux déjà présents, un par un, la vérification d'absence de doublons sur un nouvel enregistrement qu'on veut rajouter est un algorithme :
A : à coût constant (Θ(1))
B : à coût linéaire (Θ(n))
C : à coût quadratique (Θ(n2))
D : à coût cubique (Θ(n3))
6.2 Doublons absolus
Les doublons absolus : les deux entrées sont entièrement similaires, même l'identifiant unique ! Ces doublons peuvent apparaître si on fusionne des tables entre elles alors qu'elles sont issues initialement d'une même collection.
Exemple :
on extrait dans une nouvelle table les Pokemons ayant plus que 150 en Attaque.
On fusionne les résultats dans une unique table : si on ne fait pas attention, on va nécessairement se retrouver avec des doublons absolus : tous les Pokemons qui ont une attaque supérieure à 150 ET une défense supérieure à 150.
Dans cette partie, nous allons donc modifier notre création de collection à partir du CSV : nous allons vérifier qu'un enregistrement n'existe pas déjà AVANT de le rajouter dans notre collection : on ne peut pas avoir une confiance absolue en un fichier qu'on a récupéré sur Internet.
6.3 Doublons relatifs
Les doublons relatifs : les deux entrées identifient un même objet mais sont sinon un peu différentes.
Ces doublons sont réellement problématiques car les données ne sont pas vraiment identiques alors qu'il s'agit bien d'une entité unique qui devrait normalement n'avoir qu'une version !
Cas des Pokemons : la personne renseignant le fichier CSV a peut-être rempli deux fois les informations sur un même Pokemon sur deux lignes très éloignées. On aura ainsi un identifiant différent MAIS le Pokemon présent deux fois.
Cas des listes de personnes : il faut vérifier si le p-uplet (nom, prénom, numéro de sécu) n'apparaît qu'une fois. Nom Prénom n'est pas suffisant pour savoir que c'est bien un doublon.
Cas des strings : un nom avec Majuscule ou sans majuscule. Un nom composé avec un espace ou un tiret...
Cela peut arriver lors de rajouts : un utilisateur oublie qu'il a déjà crée un compte chez vous et se réinscrit sous le même nom, prénom, email... Si vous ne faites pas attention et que vous acceptez cette nouvelle inscription, votre table des clients risque rapidement de ne plus être exploitable : un même utilisateur va apparaître dans plusieurs enregistrements.
Moralité : demandez vous ce qui doit identifier à coup sur une entrée unique.
Dans le cas de nos Pokemons, c'est ici juste le nom qui importe. Il suffit juste de vérifier que le descripteur Name des autres enregistrements n'est pas identique à celui-ci.
18° Réaliser les actions suivantes.
Ouvrir le fichier creation_collec.py.
On y trouve dans la fonction creer_collection() le code qui rajoute automatiquement le nouvel enregistrement sans vérifier s'il s'agit d'un doublon dont le double est déjà présent dans la collection.
53
54
55
56
57
# 4 - On lit la suite du fichier ligne par ligneforligneinfichier:# pour chaque ligne du fichier-textevaleurs=decomposer(ligne,',')# extraction des valeurs dans un tableaunouveau=creer_enregistrement(valeurs,attributs)collec.append(nouveau)# rajout de l'enregistrement à la collection
Remplacer ce code par le suivant : on vérifie si un Pokemon portant le même nom existe déjà dans la collection avant de faire le append().
Pour cela, on crée une variable booléenne doublon. Si elle devient True, on sort de la boucle de lecture des enregistrements un à un : puisque qu'on sait que c'est un doublon, ce n'est plus la peine de vérifier les autres fiches, c'est une perte de temps.
Nouveauté : comment sortir d'une boucle avant qu'elle ne soit finie ? Il faut utiliser le mot-clé break. On arrête alors immédiatement la boucle et on continue le programme sur la première instruction derrière la boucle. ATTENTION : il faut vraiment éviter de l'utiliser. Même problème que les fonctions avec plusieurs sorties : àa devient compliqué de prouver la correction.
# 4 - On lit la suite du fichier ligne par lignenumero_unique=1forligneinfichier:# pour chaque ligne du fichier-textevaleurs=decomposer(ligne,',')# extraction des valeurs dans un tableaunouveau=creer_enregistrement(valeurs,attributs)doublon=Falseforenregistrementincollec:ifnouveau['Name']==enregistrement['Name']:doublon=Truebreak# On stoppe la boucleifnot(doublon):# Si aucun autre dictionnaire ne porte le même nomnouveau['cle_primaire']=numero_uniquenumero_unique=numero_unique+1collec.append(nouveau)# rajout à la collectionelse:print('Doublon relatif')print(nouveau)
✎ 19° Que doit contenir doublon pour que la condition de la ligne 63 autorise à effectuer le rajout de l'enregistrement avec append à la ligne 66 ?
Quasi-doublon
Ceux là sont plus difficile à déceler : il s'agit d'enregistrement dont la différence tient à un détail, une faute de frappe. Un prénom avec un é ou ï comme Héloïse qu'on trouve orthographié Héloise ou Hèloise. Une lettre oubliée ou déplacée : Sarah écrit Sara ou Sarha... Il existe un très grand nombre d'erreurs possibles.
Autre erreur courant : les majuscules - minuscules. C'est bien pour cela que la plupart des systèmes stockent toutes les entrées en minuscules, transformant au besoin les entrées utilisateurs en minuscules. C'est plus facile de vérifier la cohérence si on est certain que tout doit être normalement en lettres minuscules.
Elles sont bien plus difficiles à déceler que les erreurs de typage (la cohérence entre un type et une valeur est facile à programmer) ou les erreurs de doublons classiques (facile à programmer également).
Pour éviter les quasi-doublons, il faut donc une fonction qui veille à étudier en détail chaque entrée, au besoin en demandant l'intervention d'un opérateur humain pour valider telle ou telle modification.
Python possède d'ailleurs 3 méthodes applicables aux strings :
upper renvoie une copie du string en mettant tout en majuscules.
lower renvoie une copie du string en mettant tout en minuscules.
title renvoie une copie au chaque début de mot est en majuscule, les autres en minuscules.
✎ 20° En parlant de cohérence des noms utilisés en vue de vérifier la présence de quasi-doublons, quelle critique pourrait-on faire au nom de la clé nommée cle_primaire par rapport aux autres ?
Lorsqu'on le rencontre, on sort aussi mais moins fortement : on considère juste que le tour de boucle en cours est terminé : on revient au début. Si on est à l'étape 6, on passe donc immédiatement au début de la 7.
Je voudrais compter de 0 à 7 mais sans passer par 5, c'est possible ? Oui, comme ça par exemple :
Aucune connaissance n'est exigible là dessus en 1er NSI. Mais c'est très pratique.
La structure permet notamment de rendre vos applications solides face aux entrées des utilisateurs.
Demander un nombre et vous êtes presque certain que certains vont vous répondre AZERTY
Le principe de cette structure est de remplacé la levée d'exception (lorsqu'il y a une erreur), par ... autre chose qu'un arrêt du programme et un message dans la console.
On trouve 3 blocs dont les actions liées sous tabulées à droite, comme d'habitude avec Python.
Dans le bloc try, l'action qu'on veut tenter de réaliser mais qu'on soupçonne de pouvoir provoquer une erreur dans certaines conditions.
Si l'une des actions provoquent une erreur, on stoppe les instructions à cet endroit et on passe au bloc except. Cela veut dire que ce qui est situé sous l'erreur dans le bloc try ne va pas être exécuté.
Si aucune erreur n'intervient, on ne lève pas d'exception et on n'exécute donc pas le bloc except.
1
2
3
4
5
6
7
8
9
10
11
12
continuer=Truewhilecontinuer:a=input('Entrez un nombre entier : ')b=input('Entrez un autre nombre entier : ')try:a=int(a)b=int(b)c=a+bcontinuer=Falseexcept:print('Des nombres entiers ! Nan mais quoi !')print(f'{a}+{b} = {c}')
Voici un exemple d'utilisation :
Entrez un nombre entier : 8
Entrez un autre nombre entier : A
Des nombres entiers ! Nan mais quoi !Entrez un nombre entier : 8
Entrez un autre nombre entier : 15
8+15 = 23
Activité publiée le 15 03 2020
Dernière modification : 15 03 2020
Auteur : ows. h.