Python: La compréhension de listes et de dictionnaires

La compréhension de listes et de dictionnaires en Python est une méthode très puissante pour transposer un texte structuré en liste, dictionnaire etc etc.

Python: La compréhension de dictionnaires

Nous allons utiliser la même chaine de texte que pour la compréhension de liste.

>>> s = """AIN    1    01    00
AISNE    1    02    00
ALLIER    1    03    00
ALPES(BASSES-)    1    04    00
ALPES(HAUTES-)    1    05    00
ALPES-MARITIMES    1    06    00
ARDECHE    1    07    00
ARDENNES    1    08    00
ARIEGE    1    09    00
AUBE    1    10    00
AUDE    1    11    00
AVEYRON    1    12    00
BOUCHES-DU-RHONE    1    13    00
CALVADOS    1    14    00
CANTAL    1    15    00
CHARENTE    1    16    00
CHARENTE-INFERIEURE    1    17    00
CHER    1    18    00
CORREZE    1    19    00
CORSE    1    20    00
COTE-D'OR    1    21    00
COTES-DU-NORD    1    22    00
CREUSE    1    23    00
"""

Nous allons initialiser un dictionnaire, ayant en clé le nom du département (par exemple), et les données numériques dans une liste correspondante à la valeur de la clé.

>>> d = {x.split('\t')[0]: x.split('\t')[1:] for x in s.split('\n')}
>>> pprint(d)
{'': [],
 'AIN': ['1', '01', '00'],
 'AISNE': ['1', '02', '00'],
 'ALLIER': ['1', '03', '00'],
 'ALPES(BASSES-)': ['1', '04', '00'],
 'ALPES(HAUTES-)': ['1', '05', '00'],
 'ALPES-MARITIMES': ['1', '06', '00'],
 'ARDECHE': ['1', '07', '00'],
 'ARDENNES': ['1', '08', '00'],
 'ARIEGE': ['1', '09', '00'],
 'AUBE': ['1', '10', '00'],
 'AUDE': ['1', '11', '00'],
 'AVEYRON': ['1', '12', '00'],
 'BOUCHES-DU-RHONE': ['1', '13', '00'],
 'CALVADOS': ['1', '14', '00'],
 'CANTAL': ['1', '15', '00'],
 'CHARENTE': ['1', '16', '00'],
 'CHARENTE-INFERIEURE': ['1', '17', '00'],
 'CHER': ['1', '18', '00'],
 'CORREZE': ['1', '19', '00'],
 'CORSE': ['1', '20', '00'],
 "COTE-D'OR": ['1', '21', '00'],
 'COTES-DU-NORD': ['1', '22', '00'],
 'CREUSE': ['1', '23', '00']}

Par contre, notre dictionnaire contient une ligne vide (la première), nous allons donc en tenir compte lors de la création de notre dictionnaire.

>>> d = {x.split('\t')[0]: x.split('\t')[1:] for x in s.split('\n') if x.split('\t')[0] != ''}
>>> pprint(d)
{'AIN': ['1', '01', '00'],
 'AISNE': ['1', '02', '00'],
 'ALLIER': ['1', '03', '00'],
 'ALPES(BASSES-)': ['1', '04', '00'],
 'ALPES(HAUTES-)': ['1', '05', '00'],
 'ALPES-MARITIMES': ['1', '06', '00'],
 'ARDECHE': ['1', '07', '00'],
 'ARDENNES': ['1', '08', '00'],
 'ARIEGE': ['1', '09', '00'],
 'AUBE': ['1', '10', '00'],
 'AUDE': ['1', '11', '00'],
 'AVEYRON': ['1', '12', '00'],
 'BOUCHES-DU-RHONE': ['1', '13', '00'],
 'CALVADOS': ['1', '14', '00'],
 'CANTAL': ['1', '15', '00'],
 'CHARENTE': ['1', '16', '00'],
 'CHARENTE-INFERIEURE': ['1', '17', '00'],
 'CHER': ['1', '18', '00'],
 'CORREZE': ['1', '19', '00'],
 'CORSE': ['1', '20', '00'],
 "COTE-D'OR": ['1', '21', '00'],
 'COTES-DU-NORD': ['1', '22', '00'],
 'CREUSE': ['1', '23', '00']}

Et voilà, tout est nickel. Et tout ça à partir d'une simple chaine de texte.

Pour obtenir les données relatives au département de la Charente:

>>> d['CHARENTE']
['1', '16', '00']
Etiquettes: 

Python: La compréhension de listes

Par exemple, avec la chaine de texte suivante qui contient des noms de départements et quelques données numériques:

>>> s = """AIN    1    01    00
AISNE    1    02    00
ALLIER    1    03    00
ALPES(BASSES-)    1    04    00
ALPES(HAUTES-)    1    05    00
ALPES-MARITIMES    1    06    00
ARDECHE    1    07    00
ARDENNES    1    08    00
ARIEGE    1    09    00
AUBE    1    10    00
AUDE    1    11    00
AVEYRON    1    12    00
BOUCHES-DU-RHONE    1    13    00
CALVADOS    1    14    00
CANTAL    1    15    00
CHARENTE    1    16    00
CHARENTE-INFERIEURE    1    17    00
CHER    1    18    00
CORREZE    1    19    00
CORSE    1    20    00
COTE-D'OR    1    21    00
COTES-DU-NORD    1    22    00
CREUSE    1    23    00
"""

Quand on l'affiche dans une console Python:

>>> s.__str__()
"AIN\t1\t01\t00\nAISNE\t1\t02\t00\nALLIER\t1\t03\t00\nALPES(BASSES-)\t1\t04\t00\nALPES(HAUTES-)\t1\t05\t00\nALPES-MARITIMES\t1\t06\t00\nARDECHE\t1\t07\t00\nARDENNES\t1\t08\t00\nARIEGE\t1\t09\t00\nAUBE\t1\t10\t00\nAUDE\t1\t11\t00\nAVEYRON\t1\t12\t00\nBOUCHES-DU-RHONE\t1\t13\t00\nCALVADOS\t1\t14\t00\nCANTAL\t1\t15\t00\nCHARENTE\t1\t16\t00\nCHARENTE-INFERIEURE\t1\t17\t00\nCHER\t1\t18\t00\nCORREZE\t1\t19\t00\nCORSE\t1\t20\t00\nCOTE-D'OR\t1\t21\t00\nCOTES-DU-NORD\t1\t22\t00\nCREUSE\t1\t23\t00\n"

On s'aperçoit que la chaine est composée de lignes séparées par '\n' et des colonnes séparées par '\t'.

Pour convertir cette chaine de texte en une liste simple il suffit de faire:

>>> l = s.split('\n')
>>> print(l)
['AIN\t1\t01\t00', 'AISNE\t1\t02\t00', 'ALLIER\t1\t03\t00', 'ALPES(BASSES-)\t1\t04\t00', 'ALPES(HAUTES-)\t1\t05\t00', 'ALPES-MARITIMES\t1\t06\t00', 'ARDECHE\t1\t07\t00', 'ARDENNES\t1\t08\t00', 'ARIEGE\t1\t09\t00', 'AUBE\t1\t10\t00', 'AUDE\t1\t11\t00', 'AVEYRON\t1\t12\t00', 'BOUCHES-DU-RHONE\t1\t13\t00', 'CALVADOS\t1\t14\t00', 'CANTAL\t1\t15\t00', 'CHARENTE\t1\t16\t00', 'CHARENTE-INFERIEURE\t1\t17\t00', 'CHER\t1\t18\t00', 'CORREZE\t1\t19\t00', 'CORSE\t1\t20\t00', "COTE-D'OR\t1\t21\t00", 'COTES-DU-NORD\t1\t22\t00', 'CREUSE\t1\t23\t00', '']

C'est mieux car on peut maintenant parcourir la liste ligne par ligne mais ce qui suit est encore mieux:

>>> l = [x.split('\t') for x in s.split('\n')]
>>> print(l)
[['AIN', '1', '01', '00'], ['AISNE', '1', '02', '00'], ['ALLIER', '1', '03', '00'], ['ALPES(BASSES-)', '1', '04', '00'], ['ALPES(HAUTES-)', '1', '05', '00'], ['ALPES-MARITIMES', '1', '06', '00'], ['ARDECHE', '1', '07', '00'], ['ARDENNES', '1', '08', '00'], ['ARIEGE', '1', '09', '00'], ['AUBE', '1', '10', '00'], ['AUDE', '1', '11', '00'], ['AVEYRON', '1', '12', '00'], ['BOUCHES-DU-RHONE', '1', '13', '00'], ['CALVADOS', '1', '14', '00'], ['CANTAL', '1', '15', '00'], ['CHARENTE', '1', '16', '00'], ['CHARENTE-INFERIEURE', '1', '17', '00'], ['CHER', '1', '18', '00'], ['CORREZE', '1', '19', '00'], ['CORSE', '1', '20', '00'], ["COTE-D'OR", '1', '21', '00'], ['COTES-DU-NORD', '1', '22', '00'], ['CREUSE', '1', '23', '00'], ['']]

Chaque colonne est séparée dans une liste. On appelle ça une compréhension de liste.

Il est donc très facile d'accéder à une donnée en particulier:

>>> print(l[0][0])
AIN
Etiquettes: 

Python: La compréhension de listes, effectuer des opérations complexes

Voici un exemple, qui ne sert pas à grand chose, mais qui permet de montrer les différents calculs complexes qu'il est possible de faire avec la compréhension de liste.

Dans cet exemple, j'ai une classe qui permet de générer, aléatoirement, des codes EAN13.

>>> from random import randint
>>> class EAN13():
    GENERATES = []
    def __init__(self):
        pass
    def new(self, count=1):
        ct = count
        while ct > 0:
            ean13 = '{:03}{}'.format(randint(40, 49), ''.join([((x+4)*'0'+str(randint(1, int((x+4)*'9'))))[-(x+4):] for x in range(2)]))
            ean13 += str(10 - (sum([int(y) * 3 if x % 2 == 0 else int(y) for x, y in enumerate(list(ean13), start=1)]) % 10))[-1]
            if ean13 not in EAN13.GENERATES:
                EAN13.GENERATES.append(ean13)
                ct -= 1
        return EAN13.GENERATES[-count:]

>>> EAN13().new(count=20)
['0467764013590', '0442457715586', '0478050754264', '0498544873950', '0419792606732', '0436177825380', '0487308428246', '0449011102394', '0487403914866', '0462030759684', '0438507088359', '0405068027851', '0489779856153', '0437057321695', '0465499916742', '0467772469648', '0493671951373', '0483943989975', '0499830229161', '0462787735498']

Comme indiqué sur wikipédia:

Un code EAN13 est composé de 13 chiffres

  • les deux ou trois premiers correspondent au pays de provenance du produit, ou à une classe normalisée de produits ;
  • les 4 ou 5 suivants sont le numéro de membre de l’entreprise participant au système EAN ;
  • les 4 ou 5 suivants sont le numéro d’article du produit ainsi marqué et
  • le treizième est une clé de contrôle calculée en fonction des douze précédents.

Je vais "exploser" mon code pour expliquer les différentes étapes.

Voici la ligne qui permet de générer aléatoirement les 12 premiers chiffres:

>>> ean13 = '{:03}{}'.format(randint(40, 49), ''.join([((x+4)*'0'+str(randint(1, int((x+4)*'9'))))[-(x+4):] for x in range(2)]))

Les 3 premiers chiffres de mon code, ceux correspondant au pays de provenance du produit, ou à une classe normalisée de produits, est un nombre aléatoire allant de 040 à 049 (à l'aide la fonction randint et format)

>>> '{:03}{}'.format(randint(40, 49), '')
'041'

Voici la fameuse compréhension de liste qui va permettre de générer deux nombres.
Le premier composé de 4 chiffres et le second composé de 5 chiffres.

>>> [((x+4)*'0'+str(randint(1, int((x+4)*'9'))))[-(x+4):] for x in range(2)]
['2983', '23696']

Si nous faisions la même chose mais sans utiliser la compréhension de liste, ça donnerait ceci:

>>> L = []
>>> for x in range(2):
    L.append(((x+4)*'0'+str(randint(1, int((x+4)*'9'))))[-(x+4):])
   
>>> L
['5237', '92948']

J'utilise donc ma boucle for pour gérérer la première fois (x=0) un nombre de 4 chiffres et la fois suivante (x=1) un nombre de 5 chiffres.

J'utilise également le slicing ([-(x+4):]) pour conserver uniquement les x derniers chiffres de mes deux nombres aléatoires auquels j'ai ajoutés des '0' à gauche pour être certain d'avoir le bon nombre de chiffres.

J'aurais également pû utiliser la fonction format comme ceci:

>>> L.append('{0:0{1}}'.format(randint(1, int((x+4)*'9')), x+4))

Il ne reste plus qu'à calculer la clé qui sera donc le treizième et dernier chiffre de notre code.

Voici donc la ligne de code qui permet de le faire:

>>> ean13 += str(10 - (sum([int(y) * 3 if x % 2 == 0 else int(y) for x, y in enumerate(list(ean13), start=1)]) % 10))[-1]

Cette ligne de code utilise également la compréhension de liste.

J'utilise donc une boucle for et la fonction enumerate qui permet d'indexer chaque chiffres de mon code.

Je vais donc pouvoir faire la somme de tous mes chiffres et en ayant multiplié par 3 les rangs pairs (comme indiqué dans la formule de calcul de la clé).

>>> list(ean13)
['0', '4', '6', '5', '0', '3', '9', '9', '9', '0', '9', '9']
>>> [int(y) * 3 if x % 2 == 0 else int(y) for x, y in enumerate(list(ean13), start=1)]
[0, 12, 6, 15, 0, 9, 9, 27, 9, 0, 9, 27]
>>> sum([int(y) * 3 if x % 2 == 0 else int(y) for x, y in enumerate(list(ean13), start=1)])
123

Ci-dessous le reste du code détaillé ligne par ligne pour obtenir la clé finale (le caractère '_' représentant le résultat de la dernière exécution)

>>> _ % 10
3
>>> 10 - _
7
>>> str(_)[-1]
'7'

Pour info:
La première ligne permet d'obtenir le reste de la division par 10 (123%10=3)
La seconde ligne permet de soustraire à 10 le chiffre précédement obtenu.
La troisième ligne permet uniquement de garder le bon chiffre, dans le cas où le reste de la division est égal à 0.

La compréhension de list en Python est vraiment très puissante.
Elle permet de faire beaucoup de choses d'une manière plus concentrée et parfois plus facile à comprendre.

J'espère avoir été assez clair dans mes explications...

Etiquettes: