Slicing : des tranches en Python

Il y a quelques jours, dans l’article vous montrant comment inverser une chaine de caractères en Python, j’ai utilisé le slicing. C’est un outil indispensable en Python qui permet de découper une séquence (liste, chaine de caractères ou tuple).

Le slicing a une syntaxe particulière et surtout un comportement déroutant : la borne supérieure est exclue. Je vais dans cet article vous expliquer pourquoi Guido Van Rossum a choisi ce comportement et vous illustrer ceci par un exemple.

Les bases du slicing

Pour l’illustration, je vais utiliser une liste de chaines de caractères. Gardons l’esprit Monthy Python Sacré Graal :

In [1]: camelot = ['Arthur', 'Lancelot', 'Robin', 'Galahad', 'Bedivere', 'Perceval']

Le slicing nous permet donc d’extraire une tranche de cette liste. La syntaxe utilise les crochets où les bornes inférieure et supérieure sont séparées par deux points :

In [2]: camelot[2:4]                                                            
Out[2]: ['Robin', 'Galahad']

Vous voyez que cette tranche part bien de l’élément à l’indice inférieur mais que l’élément à l’indice supérieur (ici 4 soit Bedivere) est exclu. Le slicing en Python est donc un ensemble semi-ouvert à droite. J’explique pourquoi Guido Van Rossum a choisi ce comportement dans la partie suivante.

Auparavant, précisons que si vous souhaitez créer une tranche qui commence au début de la séquence ou qui finit à la fin, vous pouvez omettre cette borne. Vous pouvez également utiliser les indices négatifs. Les instructions suivantes signifient respectivement les deux premiers éléments et les deux derniers éléments.

In [3]: camelot[:2]                                                             
Out[3]: ['Arthur', 'Lancelot']

In [4]: camelot[-2:]                                                            
Out[4]: ['Bedivere', 'Perceval']

Enfin, vous pouvez ajouter un troisième paramètre qui est le pas. Ainsi, l’instruction suivante retourne une liste de tous les éléments impaires :

In [5]: camelot[::2]                                                            
Out[5]: ['Arthur', 'Robin', 'Bedivere']

Et ce qui est intéressant, c’est qu’avec un pas négatif, on inverse la séquence.

In [6]: camelot[::-1]                                                           
Out[6]: ['Perceval', 'Bedivere', 'Galahad', 'Robin', 'Lancelot', 'Arthur']

Il est intéressant de voir que si les bornes dépassent la taille de la séquence, le slicing retourne une séquence vide. Ceci va être utile.

In [7]: camelot[10:]                                                           
Out[7]: []

Le Slice est aussi un objet

Python permet de définir un objet slice à l’aide de la fonction du même nom :

In [15]: my_slice = slice(2, 4)

Cette fonction prends de 1 à 3 paramètres qui sont des entiers.
Avec un seul paramètre, il s’agit de la borne de fin. Avec deux paramètres, il s’agit des bornes de début et de fin. Et enfin, le troisième paramètre ajoute le pas.
L’objet slice peut être utilisé comme /paramètre/ dans les crochets :

In [16] : camelot[my_slice]
Out[16]: ['Robin', 'Galahad']

Ainsi, nous pouvons représenter dans nos programmes les tranches avec un objet plutôt qu’avec des bornes. Nous pouvons ainsi gagner en lisibilité.

Prenons le cas où nous voulons sélectionner les gagnants de compétitions sportives, c’est à dire les 3 premiers de la liste ordonnée selon le classement. Avec l’informations de borne, nous aurons un code comme le suivant :

winners_max = 3
winners = competitors[:winners_max]

Et avec l’objet slice :

winners_slice = slice(3)
winners = competitors[winners_slice]

Vous remarquerez d’ailleurs l’intérêt de l’objet si on modifie les bornes. Dans le premier cas, il est impossible d’ajouter une borne de début sans devoir modifier tout le code. Dans me second, il suffit de passer le bon slice.

En passant, vous remarquerez que nous pouvons ainsi aussi jouer sur le duck typing : winners_slice peut aussi bien être un slice qu’un entier et ainsi récupérer en fonction du contexte une séquence d’éléments ou un seul.

Pourquoi un ensemble semi-ouvert ?

Guido Van Rossum a choisi pour le slicing une notation donnant des ensembles semi-ouverts. Ce choix peut paraître déroutant, mais il est très pratique pour ce à quoi servent les slices : faire des tranches. Allons-y en prenant nos chevaliers de Camelot et faisons des groupes de 2. Peu importe la taille du groupe, ce nombre d’éléments sera dans une variable. Nous aurons donc le premier groupe retourné de la manière suivante :

In [17]: size = 2                                                                

In [18]: camelot[:size]                                                            
Out[18]: ['Arthur', 'Lancelot']

Vous voyez que pour la première passe, la taille souhaitée est l’indice de fin. Et le début du groupe suivant ? Et bien puisque l’ensemble est semi-ouvert à droite, l’indice de début du groupe suivant est simplement l’indice de fin de son précédent soit

In [19]: camelot[size:]                                                         
Out[19]: ['Robin', 'Galahad', 'Bedivere', 'Perceval']

Qui nous permet ici d’avoir le reste du groupe.

Et pour avoir des groupes de deux, il suffit d’itérer tant que le slicing produit une liste avec des éléments. En utilisant la propriété que en Python, une liste vide a un sens de faux, le code est :

In [20]: start = 0                                                              
In [21]: size = 2                                                               
In [22]: while camelot[start:start + size]: 
    ...:     print(camelot[start:start + size]) 
    ...:     start += size 
    ...:                                                                        
['Arthur', 'Lancelot']
['Robin', 'Galahad']
['Bedivere', 'Perceval']

Bonus Python 3.8 : le Walrus Operator

Le Walrus Operator noté := permet d’assigner une valeur à une variable au sein d’une expression. Ceci permet d’économiser un appel à une instruction dans les cas où on testerait cette instruction et si la condition est bonne, on rappelle cette instruction pour son résultat. L’instruction précédente devient alors :

>>> size = 2
>>> start = 0
>>> while group := camelot[start:start + size]:
...     print(group)
...     start += size
... 
['Arthur', 'Lancelot']
['Robin', 'Galahad']
['Bedivere', 'Perceval']
>>> 

Voilà, le slicing n’a plus de secret pour vous !

Si vous avez aimé ce post, n’hésitez pas à laisser un commentaire ci-dessous ou sur la page Facebook 😉

À propos de... Darko Stankovski

iT guy, photographe et papa 3.0, je vous fais partager mon expérience et découvertes dans ces domaines. Vous pouvez me suivre sur les liens ci-dessous.

Vous aimerez aussi...

3 réponses

  1. 1 décembre 2019

    […] s’applique à toutes les séquences, je vous invite à consulter la documentation ou au moins mon article dédié. Le troisième paramètre permet de spécifier un pas qui peut être négatif. Ainsi, vous pouvez […]

  2. 10 février 2020

    […] mon article sur le slicing, je vous ai proposé un exemple de code combinant l’opérateur while et le walrus operator […]

  3. 24 mai 2022

    […] Le slicing est un outils très pratique pour couper des tranches d’une séquence. NumPy, c’est la bibliothèque qui permet de travailler efficacement avec des matrices. Elle est la base de nombreux projets d’analyse de données comme matplotlib ou Pandas. […]

Laisser un commentaire

En naviguant sur Dad 3.0, vous acceptez l’utilisation de cookies pour une navigation optimale et nous permettre de réaliser des statistiques de visites. Plus d'informations

Le blog Dad 3.0 utilise les cookies pour vous permettre une navigation optimale et nous permettre de réaliser des statistiques de visite. Dad 3.0 affichant des publicités, celles-si utilisent également des cookies pour un ciblage publicitaire. En continuant la navigation sur Dad 3.0, vous acceptez le dépôt et la lecture de cookies.

Fermer