.. _test-python:
Test technique
==============
Le contexte
-----------
Ce cours suppose `les bases du langage Python `_ maitrisées. Le test technique ci dessous est là pour s'en assurer.
Ce test couvre la lecture de données, les principales structures de données, l'organisation du code en fonctions et les doctests. Le réussir atteste que vous savez manipuler les éléments essentiels du langage.
Chaque fonction doit être évaluée par les doctests associés. Vous devez obtenir 100 % en 03:00 maximum. Si ce n'est pas le cas, les compétences de base doivent être renforcées.
Le sujet
--------
Les communes en France
......................
En France, `la commune `_ est à la fois une collectivité territoriale et une circonscription administrative.
`La réforme des collectivités territoriales de 2010 `_ oblige chaque commune à adhérer à un `Etablissement Public de Coopération Intercommunale `_, qui peut être :
- `une communauté de communes `_ ;
- `une communauté d'agglomération `_ ;
- `une communauté urbaine `_ ;
- `une métropole `_.
Les données
...........
Les données sont produites par l'`INSEE `_ et enrichies et mises en ligne par `opendatasoft `_. Elles sont disponibles dans le fichier :download:`population.csv <../data/population.csv>`. Ce fichier est encodé en ``utf8``.
Les champs sont désignés ci dessous :
- Code Officiel Région ;
- Nom Officiel Région ;
- Code Officiel Département ;
- Code Officiel Arrondissement Départemental ;
- Code Officiel Commune / Arrondissement Municipal ;
- Nom Officiel Commune / Arrondissement Municipal ;
- Population municipale ;
- Population comptée à part ;
- Population totale ;
- Année de recensement ;
- Année d’entrée en vigueur ;
- Année de référence géographique ;
- Nom Officiel EPCI ;
- Code Officiel EPCI ;
- Nom Officiel Département.
L'objectif est d'écrire un ensemble de fonctions permettant de lire et manipuler ces données. On utilisera le fichier squelette :download:`population.py <../files/population.py>`.
Lecture des données
...................
Ecrire une fonction :func:`read_file` :
- qui prend en argument un nom de fichier sous forme de chaîne de caractères
- et retourne une :class:`list` de :class:`dict`, un par ligne de données.
Jeter un oeil aux doctests pour des exemples d'appel/retour de la fonction.
Quelques indications :
- il sera très pratique d'utiliser la classe :class:`~csv.DictReader` du module :mod:`csv` ;
- lors de l'ouverture du fichier, on portera une attention particulière à l'encodage du fichier csv ;
- lors de la lecture, on portera une attention particulière au délimiteur utilisé.
Utiliser la fonction :func:`main` pour la mise au point, puis valider la fonction en exécutant les doctests associés.
Construire la liste des départements
....................................
Ecrire une fonction :func:`build_list_departements` :
- qui prend en argument la liste de dictionnaires retournée par :func:`read_file`
- et retourne la :class:`list` ordonnée des :class:`tuple` composés de la façon suivante :
- Code Officiel Département (:class:`str`) ;
- Nom Officiel Département (:class:`str`).
Jeter un oeil aux doctests pour des exemples d'appel/retour de la fonction.
Quelques indications :
- une structure de données est particulièrement appropriée pour construire une liste sans doublons ;
- pour certaines lignes, le département n'est pas renseigné ;
- la fonction :func:`sorted` est utile pour trier une séquence.
Utiliser la fonction :func:`main` pour la mise au point, puis valider la fonction en exécutant les doctests associés.
Construire la liste des communes
................................
De façon similaire, écrire une fonction :func:`build_list_communes` :
- qui prend en argument la liste de dictionnaires retournée par :func:`read_file`
- et retourne, pour les communes incluses dans les données, la :class:`list` ordonnée des :class:`tuple` composés de la façon suivante :
- Code Officiel Commune / Arrondissement Municipal (:class:`str`);
- Nom Officiel Commune / Arrondissement Municipal (:class:`str`).
Jeter un oeil aux doctests pour des exemples d'appel/retour de la fonction.
Quelques indications :
- une structure de données est particulièrement appropriée pour construire une liste sans doublons ;
- la fonction :func:`sorted` est utile pour trier une séquence ;
- on peut passer une clé de tri multi critères à la fonction :func:`sorted`.
Utiliser la fonction :func:`main` pour la mise au point, puis valider la fonction en exécutant les doctests associés.
Rechercher la population d'une commune
......................................
Ecrire une fonction :func:`get_pop_commune` :
- qui prend en argument la liste de :class:`dict` retournée par :func:`read_file` et le code de la commune (:class:`str`) ;
- et retourne un :class:`tuple` composé de la façon suivante :
- nom de la commune (:class:`str`) ;
- population totale de la commune (:class:`int`), pour l'année de recensement la plus récente ;
- année de recensement la plus récente (:class:`str`).
Jeter un oeil aux doctests pour des exemples d'appel/retour de la fonction.
Quelques indications :
- si la commune n'existe pas (ou est mal orthographiée), la fonction ne retourne rien ;
- les données dans le fichier ne sont pas forcément ordonnées ;
- il sera judicieux de ne pas parcourir plus de données que nécessaire.
Utiliser la fonction :func:`main` pour la mise au point, puis valider la fonction en exécutant les doctests associés.
Construire un dictionnaire des départements
...........................................
Ecrire une fonction :func:`build_dict_departements` :
- qui prend en argument la liste de dictionnaires retournée par :func:`read_file` ;
- et retourne un :class:`dict` dont la clé est le département (:class:`str`) et la valeur un :class:`tuple` composé de la façon suivante :
- nombre de communes du département (:class:`int`) ;
- population totale du département (:class:`int`) lors de l'année de recensement la plus récente.
Jeter un oeil aux doctests pour des exemples d'appel/retour de la fonction.
Quelques indications :
- les données réelles sont rarement (jamais ?) idéalement structurées. Ici l'ambigüité dans la désignation de la commune peut générer des erreurs dans les doctest. La source d'une éventuelle ambigüité :
- un même code INSEE/EPCI peut être attribué à deux communes différentes. Par exemple, ``Roussac`` et ``Saint-Pardoux-le-Lac`` ont le même code INSEE (``87128``) ;
- l'orthographe a pu évoluer entre deux années de recensement : ``Coeuvres-et-Valsery`` puis ``Cœuvres-et-Valsery``, ou ``Les Trois Châteaux`` puis ``Les Trois-Châteaux`` ;
- il y a pu y avoir des regroupements de communes entre deux années de recensement. Par exemple, la fusion des communes de ``Conilhac-de-la-Montagne`` et de ``Roquetaillade`` a donné naissance à la commune de ``Roquetaillade-et-Conilhac`` qui a le statut de `commune nouvelle `_ ;
- selon la façon dont sont gérées les données ambigües, l'exécution des fonctions peut conduire à un résultat différent de ceux utilisés pour les doctests ;
- un dictionnaire multi dimensionnel est une structure de données appropriée pour ce problème ;
- avec le :class:`dict` standard, il faut gérer la présence/absence de clés. En supposant que ``data`` est ici une liste de tuples ::
d = dict()
for dpt, commune, annee, pop in data:
if dpt not in d.keys(): d[dpt] = dict()
if commune not in d[dpt].keys(): d[dpt][commune] = dict()
if annee not in d[dpt][commune].keys(): d[dpt][commune][annee] = dict()
d[dpt][commune][annee] = pop
- l'utilisation d'un :class:`~collections.defaultdict` à plusieurs dimensions facilite le travail::
from collections import defaultdict
def multi_dict(K, type):
if K == 1:
return defaultdict(type)
else:
return defaultdict(lambda: multi_dict(K-1, type))
md = multi_dict(3, str)
for dpt, commune, annee, pop in data:
md[dpt][commune][annee] = pop # si la clé n'existe pas, un :class:`~collections.defaultdict` est créé
Utiliser la fonction :func:`main` pour la mise au point, puis valider la fonction en exécutant les doctests associés.
Extraire les données par département
....................................
Ecrire une fonction :func:`stat_by_dpt` :
- qui prend en argument le dictionnaire des départements (retourné par build_dict_departements()) et le département d'intérêt (str) ;
- et retourne un tuple dont le premier élément est le nombre de communes du département (:class:`int`) et le second élément la population totale du département (:class:`int`).
Utiliser la fonction :func:`main` pour la mise au point, puis valider la fonction en exécutant les doctests associés.