Contexte

Python est un langage de programmation très abordable que l’on démarre en programmation ou que l’on connaisse déjà d’autres langages. Néanmoins, ce n’est pas parce qu’un langage est abordable qu’il n’a pas ses propres idiomes et qu’il n’y a pas des trucs & astuces à connaître et à retenir.

Ci-après, une petite note d’apprentissage de Python, sur la manière d’inverser les éléments d’une liste et d’une chaine de caractères.

NB : c’est du Python 3 qui est utilisé.

Liste

Pour inverser les éléments d’une liste, il y a d’abord la méthode reverse() des listes. Cette méthode s’appelle directement sur la liste et inverse les éléments de la liste en modifiant la liste. Cela sera plus clair avec un exemple :

1
2
3
4
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print('liste de nombres :', numbers)
numbers.reverse()
print('liste de nombres :', numbers)

Si on exécute ces lignes, cela nous donne :

1
2
liste de nombres : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
liste de nombres après reverse() : [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

La liste est donc irrémédiablement modifiée. Cela peut correspondre à votre besoin, mais dans tous les cas, il vaut mieux le savoir. Maintenant, que se passe-t-il si je veux inverser l’ordre des éléments de la liste sans modifier la liste. Nous avons 2 options : utiliser la fonction reversed() et mettre à profit les slices.

Renversement de situation

On peut utiliser la fonction reversed() qui prend en paramètre une liste et retourne une liste dont les éléments sont dans l’ordre inverse. Cette fois-ci la liste originale n’est plus modifiée :

1
2
3
4
5
6
numbers = list(range(10))
print('liste de nombres :', numbers)
reversed(numbers)
print('liste de nombres après avoir appliqué reversed dessus :', numbers)
reversed_numbers = reversed(numbers)
print("liste de nombres dans l'ordre inverse obtenue avec reversed :", list(reversed_numbers))

ce qui donne :

1
2
3
liste de nombres : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
liste de nombres après avoir appliqué reversed dessus : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
liste de nombres dans l'ordre inverse obtenue avec reversed : [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

Comme on peut le constater dans l’exemple, reversed() ne modifie pas la liste elle-même. Elle retourne un iterable dont la séquence d’éléments est dans l’ordre inverse de la séquence de départ. C’est un iterable qui est retourné, c’est pour cela que pour pouvoir le faire s’afficher sous forme de liste dans le dernier print, on le convertit d’abord en liste avec list(reversed_numbers).

Bien sûr, si vous voulez itérer sur une séquence dans l’ordre inverse, vous allez utiliser reversed(), et oui bien sûr cela fonctionne sur d’autres itérables que les listes (dans l’exemple ci-après avec un range), si l’inversion a du sens :

1
2
3
print('Compte à rebours :')
for i in reversed(range(10)):
    print(i)

ce qui donne :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Compte à rebours :
9
8
7
6
5
4
3
2
1
0

Si vous connaissez un peu Python, vous ne manquerez pas de voir une similitude entre d’une part la méthode reverse() et la fonction reversed() et d’autre part la méthode sort() et la fonction sorted().

A noter que cela fonctionne également pour les tuples :

1
2
3
>>> tuple=(1,2,3,4)
>>> [i for i in reversed(tuple)]
[4, 3, 2, 1]

La tranche à l’envers

Les slices sont un outil pervasif en Python dès que l’on a besoin de manipuler des listes ou des chaines de caractères. Sans rentrer dans le détails du fonctionnement des slices auxquelles il faudrait consacrer un billet spécifique, je vais juste les présenter rapidement afin que l’on voit comment on peut les mettre à profit pour inverser notre liste.

Il faut se rappeler qu’en Python, classiquement les éléments d’une listes sont indexés de 0 (indice du premier élément) à la taille de la liste moins un (indice du dernier élément). La liste ['a', 'b', 'c', 'd', 'e', 'f'] à 6 éléments indexés de 0 à 5. Moins classiquement, on peut utiliser des indices négatifs : le dernier élément à l’indice -1 et le premier élément à l’indice -(taille de la liste). Sur la liste précédente les indices négatifs vont ainsi de -6 à -1.

Indices d’une liste en Python

En Python, on peut indiquer un indice (pour le compte positif ou négatif) pour récupérer l’élément de la liste. Par exemple, dans l’interptéteur on pourra avoir :

1
2
3
4
5
>>> liste = ['a', 'b', 'c', 'd', 'e', 'f']
>>> liste[5]
'f'
>>> liste[-1]
'f'

Mais il est également possible de préciser une tranche, un segment de la liste que l’on souhaite récupérer (en anglais, une slice), en précisant entre les crochets, un indice de départ, un indice de fin (élément associé à cet indice non-inclus dans la slice créé), et un pas d’avancement, chaque valeur séparée de la précédente par deux points (:). Par exemple, en poursuivant sur l’exemple précédent dans l’interpréteur :

1
2
>>> liste[3:6:1]
['d', 'e', 'f']

Sur le même principe que précédemment, on peut préciser des indices négatifs et un pas d’avancement négatifs. Un pas d’avancement négatifs de changer le sens de parcours. Par exemple, toujours en poursuivant l’exemple précédent dans l’interpréteur.

1
2
3
4
5
6
7
8
>>> liste[3:6:1]
['d', 'e', 'f']
>>> liste[-1:-5:-1]
['f', 'e', 'd', 'c']
>>> liste[-5:-2:1]
['b', 'c', 'd']
>>> liste[-3:6:1]
['d', 'e', 'f']

On peut mélanger des indices négatifs et positifs avec un pas d’avancement positif ou négatifs, il faut juste que l’indice de début et celui de fin soit cohérent par rapport au pas d’avancement donné : pour un pas d’avancement positif, il faut que l’indice de début soit plus petit que l’indice de fin et pour un pas d’avancement négatif, que l’indice de début soit plus grand que l’indice de fin, sinon on obtient une liste vide. En continuant avec l’interpréteur :

1
2
3
4
5
6
>>> liste[-4:-2:-1]
[]
>>> liste[3:6:-1]
[]
>>> liste[6:-3:1]
[]

Avec la syntaxe des slices, on peut ne pas préciser la valeur de l’indice de départ, la valeur de l’indice de fin ou celui du pas d’avancement. Dans ce cas des valeurs par défaut sont utilisés. Ainsi si on ne précise aucune valeur, on va obtenir une copie de la liste :

1
2
3
4
5
liste2 = liste[::]
>>> liste2 == liste
True
>>> liste2 is liste
False

Par défaut la valeur de l’indice de la liste est 0, celui de l’indice de fin est la taille de la liste et le pas d’avancement est 1. Regardez maintenant ce qui se passe si je précise une valeur de pas d’avancement de -1 :

1
2
>>> liste[::-1]
['f', 'e', 'd', 'c', 'b', 'a']

Dans ce cas, j’obtiens une copie de la liste avec les éléments dans l’ordre inverse. Vous l’avez compris, c’est notre troisième manière d’inverser l’ordre des éléments de la liste.

On notera au passage, que les valeurs par défaut pour l’indice de départ et de fin ne sont plus respectivement 0 et la taille de la liste, mais que les valeurs sont inversées. J’avoue je n’ai pas creusé pour avoir tous les détails, je suis resté au niveau de l’expérimentation.

1
2
3
4
5
6
>>> liste[::-1]
['f', 'e', 'd', 'c', 'b', 'a']
>>> liste[0:len(liste):-1]
[]
>>> liste[len(liste):0:-1]
['f', 'e', 'd', 'c', 'b']

Exemples autour des slices

A noter que cela fonctionne également pour les tuples :

1
2
3
>>> tuple=(1,2,3,4)
>>> tuple[::-1]
(4, 3, 2, 1)

Chaines de caractères

Pour les chaines de caractères, il est possible d’utiliser reversed et la slice avec un indice négatif.

Partir à la renverse

Si on utilise reversed, on n’obtient pas directement une chaine de caractères :

1
2
3
bonjour = 'Hello World'
print(bonjour)
print(reversed(bonjour))

Ce qui donne :

1
2
Hello World
<reversed object at 0x000002ADC60D88E0>

On peut quand même l’afficher moyennant un peu de travail :

1
2
3
bonjour = 'Hello World'
print(bonjour)
print("".join(reversed(bonjour)))

Avec pour résultat :

1
2
Hello World
Hello World

Néanmoins, comme pour les listes, l’intérêt d’utiliser reversed c’est pour l’utiliser dans une boucle ou dans une compréhension.

1
2
3
4
5
print(bonjour)
for c in reversed(bonjour):
    print(c)

print([c for c in reversed(bonjour)])

Ce qui donne :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Hello World
d
l
r
o
W

o
l
l
e
H
['d', 'l', 'r', 'o', 'W', ' ', 'o', 'l', 'l', 'e', 'H']

Trancher dans le vif

L’utilisation de la syntaxe d’une slice pour inverser une chaine fonctionne de la même manière que pour une liste. Cela crée une nouvelle chaine et c’est utilisable dans une boucle for ou une compréhension.

1
2
3
print(bonjour)
print(bonjour[::-1])
print([c for c in bonjour[::-1]])

Ce qui donne :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Hello World
dlroW olleH
d
l
r
o
W

o
l
l
e
H

Synthèse

Solution Impacts
méthode reverse() valable uniquement sur les listes, modifie la liste
fonction reversed() valable sur les listes, les tuples et les chaines ; retourne une nouvelle séquence avec les éléments dans l’ordre inverse
notation [::-1] valable sur les listes, les tuples et les chaines ; retourne une nouvelle séquence avec les éléments dans l’ordre inverse

Ma première impression est que la manière idiomatique de le faire en Python est d’utiliser la notation [::-1] pour inverser une chaine de caractères. De toutes façons, il faut savoir utiliser les slices en Python, on se prive quand même d’un outil bien pratique sinon.

Je trouve que dans une boucle ou une comprehension reversed véhicule clairement l’intention même si quand on est habitué à lire [::-1] on la comprend tout aussi bien. Dans tous les cas, j’aurais tendance à n’utiliser reversed que dans une boucle, pour juste récupérer une chaine ou une liste inversée la notation [::-1] semble directe. Je n’ai pas fait de tests mais j’imagine que reversed est probablement préférable pour inverser les très longues listes ou chaînes sur lesquelles on souhaiterait itérer.

Il faut connaître à mon sens la méthode reverse(), savoir qu’elle existe et ce qu’elle fait, mais contre il faut vraiment avoir un besoin spécifique : je préfère éviter une méthode qui modifie directement l’objet sous-jacent, à part si j’ai vraiment une bonne raison de le faire.

Le gist avec les exemples de code.