Une fois à l’aise en Python, on utilise souvent les dictionnaires. Et on fait souvent ça:
>>> def get(d, key, default):
... try:
... return d[key]
... except KeyError:
... return default
...
>>> d = {'a':1}
>>> get(d, 'foo', 'bar')
'bar'
>>> get(d, 'a', 'bar')
1
C’est parfaitement superflux, puisque Python le propose en standard:
>>> d.get("foo", 'bar')
'bar'
>>> d.get("a", 'bar')
1
Plus tordu encore:
>>> def get_and_set_if_not_exist(d, key, default):
... try:
... return d[key]
... except KeyError:
... d[key] = default
... return default
...
>>> d = {'a':1}
>>> get_and_set_if_not_exist(d, 'foo', []).append('wololo')
>>> d
{'a': 1, 'foo': ['wololo']}
>>> get_and_set_if_not_exist(d, 'foo', []).append('oyo oyo')
>>> d
{'a': 1, 'foo': ['wololo', 'oyo oyo']}
Python le propose aussi en standard:
>>> d = {'a':1}
>>> d.setdefault('foo', []).append('wololo')
>>> d.setdefault('foo', []).append('oyo oyo')
>>> d
{'a': 1, 'foo': ['wololo', 'oyo oyo']}
Les clés des dictionnaires n’ont pas à être des strings. N’importe quel objet hashable fait l’affaire, par exemple, des tuples:
>>> positions = {}
>>> positions[(48.856614, 48.856614)] = "Paris"
>>> positions[(40.7143528, -74.0059731)] = "New York"
>>> positions
{(48.856614, 48.856614): 'Paris', (40.7143528, -74.0059731): 'New York'}
>>> positions[(48.856614, 48.856614)]
'Paris'
Les sets sont un type de structure peu connu: ils représentent un ensemble non ordonné d’objets uniques. Il n’y a donc pas d’ordre évident dans un set, et le résultat est garanti sans doublon:
>>> e = set((3, 2, 1, 1, 1, 1, 1))
>>> e
set([1, 2, 3])
>>> e.add(1)
>>> e.add(1)
>>> e.add(14)
>>> e
set([1, 2, 3, 14])
Les opérations du set acceptent n’importe quel itérable. Y compris les opérations ensemblistes:
>>> e.update('abcdef')
>>> e
set(['a', 1, 2, 3, 'e', 'd', 'f', 'c', 14, 'b'])
>>> e = set('abc')
>>> e.union("cde")
set(['a', 'c', 'b', 'e', 'd'])
>>> e.difference("cde")
set(['a', 'b'])
>>> e.intersection("cde")
set(['c'])
Vérifier la présence l’un élément dans un set (avec l’opérateur in
) est une opération extrêment rapide (compléxité O(1)
), beaucoup plus que dans une liste ou un tuple. Le set reste pourtant itérable (mais on ne peut pas compter sur l’ordre).
Les opérateurs binaires sont overridés pour les opérations entre sets. De plus on peut utiliser une notation littérale pour décrire un set à partir de Python 2.7:
>>> {'a', 'b', 'c'} | {'c', 'd'} # union
set(['a', 'c', 'b', 'd'])
>>> {'a', 'b', 'c'} & {'c', 'd'} # intersection
set(['c'])
>>> {'a', 'b', 'c'} - {'c', 'd'} # difference
set(['a', 'b'])
Pop()
prend un argumentLa raison pour laquelle il n’y a pas de unshift
sur les listes en Python, c’est que l’on en a pas besoin:
>>
>>> l = [1, 2, 3, 4, 5]
>>> l.pop()
5
>>> l
[1, 2, 3, 4]
>>> l.pop(0)
1
>>> l
[2, 3, 4]
>>> l.pop(-2)
3
>>> l
[2, 4]
Le slicing, que l’on peut appliquer à tous les indexables (listes, tuples, strings, etc), est la fonctionalité bien pratique qui permet de récupérer une sous partie de la structure de données:
>>> l = range(10)
>>> l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> l[2:8]
[2, 3, 4, 5, 6, 7]
>>> l[5:]
[5, 6, 7, 8, 9]
>>> l[:5]
[0, 1, 2, 3, 4]
Ca vous connaissiez sûrement. Mais cette syntaxe accepte un 3eme nombre: le pas.
Le premier nombre dit d’où l’on part. Le second où l’on s’arrête. Le dernier dit de combien on avance (par défaut de 1).
>>> l[2:8:2]
[2, 4, 6]
>>> l[2::2] # chaque paramètre est optionel
[2, 4, 6, 8]
Et le pas peut être négatif, ce qui est plutôt sympas si vous voulez parcourir une liste ou une string à reculon.
>>> l[::-1]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
extend()
accepte n’importe quel itérableextend() permet de mettre à jour une liste. On l’utilise souvent en lui passant une autre liste:
>>> l = [1, 2, 3]
>>> l.extend([4, 5, 6])
>>> l
[1, 2, 3, 4, 5, 6]
Mais comme la plupart du code la bibliothèque standard, extend()
accepte n’importe quel itérable.
>>> t = (42, 666, 1024) # un tuple
>>> s = '456' # une string
>>> d = {'3.14': 'pi'} # un dico
>>> l = [1, 2, 3]
>>> l.extend(s)
>>> l
[1, 2, 3, '4', '5', '6']
>>> l.extend(d) #
>>> l
[1, 2, 3, '4', '5', '6', '3.14']
>>> l.extend(t)
>>> l
[1, 2, 3, '4', '5', '6', '3.14', 42, 666, 1024]
Ca marche aussi avec les set, les fichiers, les expressions génératrices. Attention cependant, sachez que l’itération retourne: par exemple itérer sur un dico retourne ses clés, pas ses valeurs (car on peut récupérer l’un avec l’autre, mais pas l’inverse).
Ce qui permet de créer un tuple ne sont pas les parenthèses, mais la virgule:
>>> 1,2,3 # ceci EST un tuple
(1, 2, 3)
>>> 1, # tuple
(1,)
>>> 1 # int
1
La raison pour laquelle il est recommandé d’utiliser presque TOUJOURS les parenthèses, c’est qu’elles permettent d’éviter les ambiguïtés, et qu’elles autorisent la définition sur plusieurs lignes:
>>> type(1,2,3) # tuple ou paramètres ?
Traceback (most recent call last):
File "", line 1, in
type(1,2,3)
TypeError: type() argument 1 must be string, not int
>>> type((1,2,3))
>>> (1, # un gros tuple s'écrit sur plusieurs lignes
... 2,
... 3)
(1, 2, 3)
Mais il existe des rares cas où il est acceptable de ne pas mettre de parenthèses:
>>> def debut_et_fin(lst):
... """
... Retourne le début et la fin d'une liste
... """
... debut = lst[0]
... fin = lst[-1]
... # donner l'illusion de retourner plusieurs valeurs
... # alors qu'on retourne en fait un tuple
... return debut, fin #
...
>>> debut, fin = debut_et_fin([1,2,3,4]) # unpacking
>>> debut
1
>>> fin
4
>>> debut, fin = fin, debut # variable swap
>>> debut
4
>>> fin
1
collections
En plus des collections built-in, la bibliothèque standard de Python propose un module collections
avec plein d’outils en bonus.
Des dictionnaires qui conservent l’ordre d’insertion (comme les Arrays en PHP):
>>> from collections import OrderedDict
>>> d = {} # l'ordre d'un dico n'est pas garanti
>>> d['c'] = 1
>>> d['b'] = 2
>>> d['a'] = 3
>>> d.keys()
['a', 'c', 'b']
>>> d = OrderedDict()
>>> d['c'] = 1
>>> d['b'] = 2
>>> d['a'] = 3
>>> d.keys()
['c', 'b', 'a']
Un compteur qui a une interface similaire à un dictionnaire spécialisé.
>>> from collections import Counter
>>> score = Counter()
>>> score['bob']
0
>>> score['robert'] += 1
>>> score['robert']
1
>>> score['robert'] += 1
>>> score['robert']
2
Comme vous pouvez le voir il gère les valeurs par defaut, mais en prime il compte le contenu de n’importe quel itérable:
>>> Counter([1, 1, 1, 1, 1, 1, 2, 3, 3])
Counter({1: 6, 3: 2, 2: 1})
>>> Counter('Une petite puce pique plus')
Counter({'e': 5, ' ': 4, 'p': 4, 'u': 3, 'i': 2, 't': 2, 'c': 1, 'l': 1, 'n': 1, 'q': 1, 's': 1, 'U': 1})
Des tuples qui ressemblent à des structs en C, mais itérables:
>>> from collections import namedtuple
>>> Fiche = namedtuple("Fiche", "force charisme intelligence")
>>> f = Fiche(force=18, charisme=17, intelligence=3)
>>> f
Fiche(force=18, charisme=17, intelligence=3)
>>> for x in f:
... print x
...
18
17
3
>>> f.force
18
Des dicos dont la valeur par défaut est le résultat de l’appel d’une fonction:
>>> from collections import defaultdict
>>> import datetime
>>> d = defaultdict(datetime.datetime.now)
>>> d["jour"]
datetime.datetime(2012, 7, 10, 17, 34, 7, 265222)
>>> d["jour"] # la valeur est settée
datetime.datetime(2012, 7, 10, 17, 34, 7, 265222)
>>> d["raison"] = "test"
>>> d.items()
[("jour", datetime.datetime(2012, 7, 10, 17, 34, 7, 265222)), ("raison", 'test')]
]]>