Séquences

Désigne les str, list, dict, set, tuple, bytearray, etc.

Voir cette documentation pour plus de détails.

Fonctions et lib utiles

Dans la stdlib:

et aussi itertools qui recueille quelques fonctions parfois bien utiles:

tuple

Le tuple est immuable.

Warning

(1) n’est pas un tuple, à la différence de (1,)

>>> a = (1)    # raté !
>>> type(a)
<type 'int'>

>>> a = (1, )  # gagné !
>>> type(a)
<type 'tuple'>

dict

Tableau associatif, dont la clé peut être n’importe quelle valeur immuable (str, int, tuple, etc.).

La méthode dict.items() retourne la liste complète des couples clé-valeur sous forme de tuple.

dict.iteritems() fait la même chose en renvoyant un itérateur. En python3 dict.items() a le comportement de dict.iteritems() en python2.

Les fonctions dict.setdefault() et dict.get() sont à utiliser lorsqu’on veut avoir une valeur par défaut dans un tableau associatif si la clé n’existe pas.

list

Warning

La suppression d’un élément d’une liste lors d’une itération va réorganiser la liste. On peut donc manquer des éléments.

>>> fruits = ['bananes', 'cerises', 'pommes', 'mangues']
>>> for fruit in fruits:
...     fruits.remove(fruit)
>>> fruits
['cerises', 'mangues']

str

Méthodes utiles

Formatage

>>> # MAAAAAAL, on crée 6 objets string différents
>>> text = 'text ' + str(1) + ' another text ' + str(2) + ' fini'

>>> # Bien !
>>> text = 'text %d another text %d fini' % (1, 2)
>>> text = 'text {0} another text {1} fini'.format(1, 2)
>>> text = 'text {premier} another text {second} fini'.format(premier=1, second=2)

La concatenation de chaines de caractères est beaucoup plus rapide en passant par string.join() que par concaténation directe (+). Il faut donc le préférer pour de grands ensembles de données.

Les méthodes de formatage str.upper(), str.lower(), str.title() et str.capitalize() permettent de gérer la case.

Les remplacements sont plus efficaces avec str.translate() que par str.replace() pour les caractères.

Encoding

Par défaut python2 est en ASCII et python3 en unicode. Par contre dans un termial, python détecte l’encoding du tty et accèpte donc son encodage (ex : utf-8).

Note

Il y a une différence entre la représentation unicode et l’encoding utf-8.

Python peut convertir de charset/codepage/encoding vers unicode grâce à la commande bytes.decode() et l’inverse via str.encode().

La bonne méthode est :
  1. Récupération (fichiers, args, user input, etc.),

  2. convertir vers unicode avec decode(),

  3. faire les opérations en unicode,

  4. puis faire encode() au dernier moment (avant print() ou file.write())

via Sam et Max

Warning

En python 2

>>> 'héhé'.isalpha()
False
>>> u'héhé'.isalpha()
True

En python 2 on peut forcer les objets str a adopter le même comportement qu’en python 3 avec

from __future__ import unicode_literals

List comprehension

Aussi appelé list-inextension, c’est la création de séquences directement. Par exemple

>>> fruits = ['banane', 'mangue', 'fraise', 'cerise', 'abricot', 'pomme']
>>> fruits_i = [fruit for fruit in fruits if 'i' in fruit]
>>> fruits_i
['fraise', 'cerise', 'abricot']

Ce type d’opération fonctionne avec toutes les séquences (list, tuple, dict, etc.) et est très efficace d’un point de vue CPU.

Attention cependant à ne pas utiliser les parenthèses () à la place des crochets. Celles-ci servent à la création des générateurs. Il convient d’utiliser le constructeur classique tuple.

unpacking

L’unpacking se fait grâce à l’opérateur * (splat).

En gros ça permet d’extraire des données d’un itérable. Dans certains cas c’est même automatique

>>> super_liste = [1, 2, 3]
>>> a, b, c = super_liste
>>> a
1
>>> b
2
>>> c
3

En python 3 on peut même faire de l’unpacking partiel

>>> super_liste = [1, 2, 3, 4]
>>> a, *b = super_liste
>>> a
1
>>> b
[2, 3, 4]
>>> super_liste = [1, 2, 3, 4]
>>> a, *b, c = super_liste
>>> a
1
>>> b
[2, 3]
>>> c
4

On peut aussi l’utiliser directement dans une boucle

>>> a = [[1, 'hello'],[2, 'world']]
>>> for i, word in a:
...     print("%d %s" % (i, word))
...
1 hello
2 world

Mais là où l’unpacking est surtout utile c’est pour passer des arguments à une fonction

>>> def add(a, b, c):
...     return a + b + c
...
>>> add(1, 2, 3)
6
>>> values = [1,2,3]
>>> add(*values)

Ça marche également avec les dict en argument de fonction, mais dans ce cas il faut utiliser le double *.

>>> def fonction_bizarre(arg1, arg2):
...     print("mon arg1 est {0}".format(arg1))
...     print("mon arg2 est {0}".format(arg2))
...
>>> args = {'arg1': 'hello', 'arg2': 'world'}
>>> fonction_bizarre(**args)
mon arg1 est hello
mon arg2 est world

Optimisation

L’utilisation de boucles pour parcourir des tableaux est très coûteuse, surtout lorsqu’il y a des imbrications. Tous les objets ne sont pas égaux face à ce problème, les objets “rapides” sont, dans l’ordre:

On peut également utiliser les objets array.array, qui permettent de faire des tableaux d’un seul type d’objet.

Numpy et Scipy font appel à des optimisations en C et permettent donc de gérer des objets volumineux plus facilement.

L’utilisation de Cython et PyPy permet de faire gagner en vitesse d’exécution.

On peut, quand c’est possible utiliser les générateurs, comme xrange() à la place de range().

Les list-comprehension sont plus rapides qu’une boucle for classique.

La fonction map() est également rapide, mais il vaut mieux éviter d’utiliser les lamba-functions, car elles sont ré-interprétées à chaque élément.

Enfin, les fonctions et méthodes préfixées de c* sont souvent une ré-implémentation en C du module, souvent beaucoup plus rapide.

Autres types de séquences

On peut également aller voir sur collections et le tuto sur PyMOTW pour avoir de nouveaux types (collections.namedtuple, collections.OrderedDict, etc.).

Itérateurs

Voir Iterator Types et Iterable vs. Iterators vs Generators

Générateurs

générateurs simples

À la place de créer la liste et de la charger complètement en mémoire, on peut utiliser les générateurs, qui vont ne renvoyer que l’élément nécessaire au moment opportun.

Par exemple:

>>> # va charger un tableau de 2000 entrées en mémoire
>>> a = [sum(range(x)) for x in range(0, 10, 2)]
>>> for i in a:
...     print(i)
...
0
1
6
15
28
>>> # On peut réutiliser la liste autant de fois qu'on veut

En remplaçant le [] par () on va transformer la liste en générateur. Celui-ci ne contiendra pas la totalité des éléments, mais générera ceux-ci à chaque itération:

>>> # va créer un générateur
>>> b = (sum(range(x)) for x in range(0, 10, 2))
>>> print(b)
<generator object <genexpr> at 0x1be03c0>
>>> for i in b:
...     print(i)
...
0
1
6
15
28
>>> for i in b:  # ceci n'affiche rien
...     print(i)
...

yield

Le mot clé yield est à utiliser à la place de return. La fonction est ainsi transformée en générateur et son code n’est pas éxécuté au moment de l’appel.

L’éxécution du code contenu dans le générateur n’est éxécuté que lors d’une itération. À chaque itération, le générateur va s’arréter au mot-clé yield en retourner la valeur. À l’itération suivante, le générateur va redémarrer à l’endroit où il s’était arrété.

>>> def creer_generateur():
...     for i in range(25):
...         yield i*i
...
>>> gene = cree_generateur()  # pas d'éxécution de code
>>> print(gene)
<generator object <genexpr> at 0x1be25c0>
>>> for i in gene:
...     print(i)
...
0
1
4
9
16

Voir Generator Types et l’article de Sam&Max

Il est bien sur également possible de créer des générateur non itératifs, du type de

>>> def creer_generateur_2():
...     print("un !")
...     yield
...
...     print("deux !")
...     yield
...
...     print("trois !")
...     yield
...
...     print("quatre !")
...
>>> my_generator = creer_generateur_2()
>>> next(my_generator)
un !
>>> next(my_generator)
deux !
>>> next(my_generator)
trois !
>>> next(my_generator)
quatre !
Traceback (most recent call last):
  ...
StopIteration