ProgrammationDéveloppeur Python / Data Engineer

Que se passe-t-il lorsque vous passez un objet mutable (par exemple, une liste ou un dictionnaire) à une fonction Python ? Comment éviter des modifications inattendues à l'intérieur et à l'extérieur de la fonction ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Historique de la question :

Le passage de paramètres en Python met en œuvre le principe de "call by object reference" (parfois appelé call by sharing). Cela signifie que la variable à l'intérieur de la fonction commence à pointer vers le même objet en mémoire que l'argument passé de l'extérieur.

Problème :

Si la fonction modifie l'objet mutable passé (par exemple, une liste ou un dictionnaire), les modifications sont également visibles à l'extérieur de la fonction. Cela peut conduire à des bugs difficiles à déceler, surtout si l'on s'attend à ce que la fonction ne modifie pas les données d'entrée.

Solution :

Pour éviter les effets secondaires, il est conseillé de faire une copie de l'objet à l'intérieur de la fonction ou d'utiliser des structures de données immuables. Pour copier, on utilise des méthodes standard (par exemple, list.copy() pour les listes, dict.copy() pour les dictionnaires ou copy.deepcopy()).

Exemple de code :

def append_one(xs): xs.append(1) return xs lst = [0] append_one(lst) print(lst) # [0, 1] # Comment éviter les modifications ? Faire une copie : def safe_append_one(xs): ys = xs.copy() ys.append(1) return ys lst2 = [0] safe_append_one(lst2) print(lst2) # [0]

Points clés :

  • Le passage d'un objet mutable permet de modifier son état aussi bien à l'intérieur qu'à l'extérieur de la fonction.
  • Pour éviter cela, on utilise la copie des données (shallow/deep copy).
  • Les objets immuables sont protégés contre de telles modifications.

Questions piégées.

Peut-on être sûr qu'une copie de liste via .copy() est complètement indépendante de la liste d'origine ?

Non — .copy() crée une copie superficielle. Si elle contient des objets mutables imbriqués, les modifications de ceux-ci seront également visibles dans l'original.

import copy lst = [[1, 2], [3, 4]] shallow = lst.copy() shallow[0][0] = 42 print(lst) # [[42, 2], [3, 4]] deep = copy.deepcopy(lst) deep[0][0] = 100 print(lst) # [[42, 2], [3, 4]]

Le fait de retourner un nouvel objet basé sur l'entrée garantit-il l'absence de modifications dans l'original ?

Pas toujours. Si l'objet nouveau utilise des parties de l'original (par exemple, une référence à une liste imbriquée), l'objet original peut être modifié.

def duplicate_list(xs): return xs * 2 lst = [[1], [2]] res = duplicate_list(lst) res[0][0] = 999 print(lst) # [[999], [2]]

Les arguments par défaut pour des objets mutables peuvent-ils causer des problèmes lors d'appels multiples d'une fonction ?

Oui — la valeur par défaut est calculée une seule fois lors de la définition de la fonction.

def add_item(item, container=[]): container.append(item) return container print(add_item(1)) # [1] print(add_item(2)) # [1, 2]

Erreurs typiques et anti-patterns

  • Modification de l'objet mutable passé à l'intérieur de la fonction sans en informer l'utilisateur.
  • Application de la copie superficielle pour des structures de données imbriquées (erreur avec des objets imbriqués mutables).
  • Utilisation d'objets mutables comme valeurs par défaut pour les arguments de la fonction.

Exemple de la vie réelle

Cas négatif

Dans une bibliothèque de traitement de configuration, une liste a été utilisée comme valeur par défaut, entraînant un cumul d'éléments entre différents appels de fonction. Le comportement était imprévisible et a pris longtemps à se révéler.

Avantages :

Moins de code pour des appels répétés, économie de mémoire visible.

Inconvénients :

Comportement implicite, difficultés de débogage, erreurs à long terme.

Cas positif

Utilisation de None comme valeur par défaut et création explicite d'un nouvel objet à chaque appel.

Avantages :

Prévisibilité, absence d'effets secondaires inattendus, fiabilité.

Inconvénients :

Nécessite de la conscience et un peu plus de code.