Comprendre enfin Python and list : indices, slices et copies

On manipule une liste Python, on extrait trois éléments avec un slice, et le lendemain un collègue signale un bug : la liste d’origine a été modifiée sans qu’on y touche. Ce scénario revient régulièrement dès qu’on travaille avec des listes imbriquées. Derrière la syntaxe simple des crochets se cachent des mécanismes d’indexation, de découpe et de copie qui méritent qu’on s’y arrête pour de bon.

Indices négatifs en Python list : compter à rebours sans calculer la longueur

Quand on doit récupérer le dernier élément d’une liste dont la taille varie (résultats d’une requête API, lignes d’un fichier CSV parsé), le réflexe ma_liste[len(ma_liste) - 1] fonctionne mais alourdit le code. Python propose les indices négatifs : ma_liste[-1] renvoie le dernier élément, ma_liste[-2] l’avant-dernier, et ainsi de suite.

Lire également : Comprendre Sylae : fonctionnalités et avantages du service de paie

Le piège survient quand l’indice négatif dépasse la taille de la liste. Sur une liste de quatre éléments, ma_liste[-5] lève une IndexError. Aucun bouclage silencieux, contrairement à ce que font certains langages.

Les indices négatifs simplifient l’accès aux derniers éléments sans jamais appeler len(). On les retrouve aussi dans les slices, ce qui ouvre des combinaisons puissantes.

Lire également : Monintranet en SaaS : comprendre les avantages pour votre organisation

Slice Python list : la syntaxe start, stop, step décortiquée

On part d’un cas concret : extraire les éléments d’indice 2 à 5 d’une liste de données. La syntaxe est ma_liste[2:6], pas ma_liste[2:5]. L’indice de fin (stop) est toujours exclu. C’est la source d’erreur la plus fréquente chez les développeurs qui débutent avec le slicing en Python.

Omettre start ou stop

On peut laisser vide l’un ou l’autre côté du deux-points. ma_liste[:4] prend les quatre premiers éléments (indices 0 à 3). ma_liste[4:] prend tout à partir de l’indice 4 jusqu’à la fin. ma_liste[:] copie la totalité de la liste, un idiome qu’on retrouve partout dans du code Python existant.

Le paramètre step pour extraire un élément sur deux ou inverser

Le troisième paramètre, le pas (step), change la donne. ma_liste[::2] extrait un élément sur deux. ma_liste[1::2] extrait les éléments d’indice impair. Et ma_liste[::-1] inverse la liste sans modifier l’originale, ce qui en fait une alternative lisible à reversed() quand on a besoin d’une vraie liste en retour.

Jeune développeur prenant des notes sur les indices et slices Python dans un espace de coworking moderne avec tableau blanc en arrière-plan

Un step négatif impose de penser à l’envers : le start doit être supérieur au stop. ma_liste[5:1:-1] parcourt les indices 5, 4, 3, 2. Omettre start et stop avec un pas négatif ([::-1]) reste la forme la plus claire.

Copie superficielle vs copie profonde d’une list Python

On crée une sous-liste avec un slice, on modifie un élément de la sous-liste, et la liste d’origine reste intacte. Tout va bien, tant que la liste contient des objets simples (entiers, chaînes). Le problème apparaît avec des listes imbriquées.

Prenons ce code :

original = [[1, 2], [3, 4]]
copie = original[:]
copie[0][0] = 99

Après cette opération, original[0][0] vaut aussi 99. Le slice [:] crée une copie superficielle (shallow copy) : les références aux objets internes sont dupliquées, pas les objets eux-mêmes. La méthode list.copy() produit exactement le même résultat, comme le confirme la documentation officielle de Python.

Quand utiliser copy.deepcopy sur une liste

Pour obtenir une copie totalement indépendante, on passe par le module copy :

import copy
copie_profonde = copy.deepcopy(original)

copy.deepcopy duplique récursivement chaque objet imbriqué, ce qui rend original et copie totalement indépendants. Sur des structures compatibles JSON (listes de dictionnaires, listes de listes contenant des types simples), une alternative consiste à sérialiser avec json.dumps puis recharger avec json.loads. Cette approche crée aussi de nouveaux objets indépendants.

Les retours varient sur la performance comparée de ces deux méthodes : deepcopy gère des cas plus larges (objets personnalisés, références circulaires), tandis que la piste JSON reste limitée aux types sérialisables.

  • ma_liste[:] et ma_liste.copy() produisent une copie superficielle, suffisante pour des listes d’éléments simples (int, str, float).
  • copy.deepcopy(ma_liste) duplique aussi les objets imbriqués, à utiliser dès qu’une liste contient d’autres listes ou des dictionnaires.
  • La sérialisation JSON (json.loads(json.dumps(...))) fonctionne comme copie profonde pour les structures composées uniquement de types compatibles JSON.

Slices en écriture : modifier une portion de list Python

Les slices ne servent pas qu’à lire. On peut remplacer une tranche entière d’une liste en une seule opération. C’est un outil redoutable pour restructurer des données en place.

données = [10, 20, 30, 40, 50]
données[1:3] = [200, 300, 400]

Après cette ligne, données vaut [10, 200, 300, 400, 40, 50]. On a remplacé deux éléments par trois : l’affectation par slice redimensionne la liste automatiquement. On peut aussi supprimer une tranche avec del données[1:4], ou insérer des éléments à une position donnée en assignant à un slice vide : données[2:2] = [77, 88].

Cette capacité n’existe que sur les types mutables. Les tuples, les chaînes et les ranges ne la supportent pas.

Gros plan sur les mains d'une développeuse tapant du code Python avec des copies de listes sur un clavier mécanique devant un écran sombre

Trois erreurs fréquentes avec les indices et slices Python

  • Confondre indexation et slicing : ma_liste[2] renvoie un élément, ma_liste[2:3] renvoie une liste contenant cet élément. Le type de retour change, ce qui casse les opérations en aval si on ne s’y attend pas.
  • Oublier que le stop est exclu : demander les éléments de 0 à 5 avec ma_liste[0:5] ne renvoie que cinq éléments (indices 0 à 4), pas six.
  • Modifier une copie superficielle de liste imbriquée en croyant que l’originale reste intacte. C’est le bug silencieux le plus courant, et il ne produit aucune erreur visible avant que les données ne soient corrompues.

La syntaxe des listes Python tient en quelques caractères entre crochets, mais ses implications sur la mémoire et la mutabilité demandent de la rigueur. Maîtriser la distinction entre copie superficielle et copie profonde évite des heures de débogage sur des structures de données partagées entre plusieurs parties d’un programme.