Module `copy` de Python
Cet article explique le module copy de Python.
En nous concentrant sur les différences entre les copies superficielles et profondes, nous proposons une explication claire — des mécanismes de base de la duplication d’objets jusqu’aux applications dans des classes personnalisées — accompagnée d’exemples pratiques.
YouTube Video
Module copy de Python
Le module copy de Python est le module standard pour gérer la duplication (copie) d’objets. L’objectif est de comprendre la différence entre les copies superficielles et profondes et de pouvoir contrôler le comportement de copie pour des objets personnalisés.
Bases : qu’est-ce qu’une copie superficielle ?
Ici, nous illustrons le comportement des copies superficielles pour les objets mutables tels que les listes et les dictionnaires. Une copie superficielle ne duplique que l’objet de niveau supérieur et partage les références internes.
1# Example: shallow copy with lists and dicts
2import copy
3
4# A nested list
5original = [1, [2, 3], 4]
6shallow = copy.copy(original)
7
8# Mutate nested element
9shallow[1].append(99)
10
11print("original:", original) # nested change visible in original
12print("shallow: ", shallow)Dans ce code, la liste de niveau supérieur est dupliquée, mais les sous-listes internes sont partagées par référence, de sorte que shallow[1].append(99) se répercute également dans original. C’est un comportement typique d’une copie superficielle.
Qu’est-ce qu’une copie profonde ?
Une copie profonde duplique récursivement l’objet et tous les objets qu’il référence en interne, produisant un objet indépendant. À utiliser lorsque vous souhaitez dupliquer en toute sécurité des structures imbriquées complexes.
1# Example: deep copy with nested structures
2import copy
3
4original = [1, [2, 3], {'a': [4, 5]}]
5deep = copy.deepcopy(original)
6
7# Mutate nested element of the deep copy
8deep[1].append(100)
9deep[2]['a'].append(200)
10
11print("original:", original) # original remains unchanged
12print("deep: ", deep)Dans cet exemple, les modifications apportées à deep n’affectent pas original. deepcopy copie l’ensemble du graphe d’objets, produisant une réplique indépendante.
Comment décider laquelle utiliser
Le choix du type de copie doit être déterminé par la structure de l’objet et votre objectif. Si vous ne voulez pas que les modifications des éléments internes mutables affectent l’objet d’origine, deepcopy est approprié. C’est parce qu’elle duplique récursivement l’ensemble de l’objet, créant une copie totalement indépendante.
En revanche, si dupliquer uniquement l’objet de niveau supérieur suffit et que vous privilégiez la vitesse et l’efficacité mémoire, copy (copie superficielle) est plus approprié.
- Vous voulez éviter que l’original change lorsque vous modifiez des éléments internes mutables → utilisez
deepcopy. - Dupliquer uniquement le niveau supérieur suffit et vous voulez privilégier les performances (vitesse/mémoire) → utilisez
copy(copie superficielle).
Gestion des objets intégrés et immuables
Les objets immuables (p. ex., int, str et les tuples dont le contenu est immuable) n’ont généralement pas besoin d’être copiés. copy.copy peut renvoyer le même objet lorsque c’est approprié.
1# Example: copying immutables
2import copy
3
4a = 42
5b = copy.copy(a)
6print(a is b) # True for small ints (implementation detail)
7
8s = "hello"
9t = copy.deepcopy(s)
10print(s is t) # True (strings are immutable)Comme copier des objets immuables apporte peu de bénéfice en termes d’efficacité, le même objet peut être réutilisé. Vous avez rarement à vous en soucier dans la conception d’applications.
Classes personnalisées : définir __copy__ et __deepcopy__
Si la copie par défaut ne correspond pas à ce que vous attendez pour votre classe, vous pouvez fournir votre propre logique de copie. Si vous définissez __copy__ et __deepcopy__, copy.copy et copy.deepcopy les utiliseront.
1# Example: customizing copy behavior
2import copy
3
4class Node:
5 def __init__(self, value, children=None):
6 self.value = value
7 self.children = children or []
8
9 def __repr__(self):
10 return f"Node({self.value!r}, children={self.children!r})"
11
12 def __copy__(self):
13 # Shallow copy: create new Node, but reuse children list reference
14 new = self.__class__(self.value, self.children)
15 return new
16
17 def __deepcopy__(self, memo):
18 # Deep copy: copy value and deepcopy each child
19 new_children = [copy.deepcopy(child, memo) for child in self.children]
20 new = self.__class__(copy.deepcopy(self.value, memo), new_children)
21 memo[id(self)] = new
22 return new- En implémentant
__copy__et__deepcopy__, vous pouvez contrôler de manière flexible le comportement de copie propre à la classe. Par exemple, vous pouvez gérer des cas où certains attributs doivent faire référence au même objet (le partager), tandis que d'autres attributs doivent être dupliqués en tant qu'objets entièrement nouveaux. - L'argument
memopassé à__deepcopy__est un dictionnaire qui enregistre les objets déjà traités lors de la copie récursive et empêche les boucles infinies dues aux références circulaires.
1# Build tree
2root_node = Node('root', [Node('child1'), Node('child2')])
3
4shallow_copy = copy.copy(root_node) # shallow copy
5deep_copy = copy.deepcopy(root_node) # deep copy
6
7# Modify children
8# affects both root_node and shallow_copy
9shallow_copy.children.append(Node('child_shallow'))
10# affects deep_copy only
11deep_copy.children.append(Node('child_deep'))
12
13# Print results
14print("root_node:", root_node)
15print("shallow_copy:", shallow_copy)
16print("deep_copy:", deep_copy)- Dans ce code, la variable
shallow_copyest créée via une 'copie superficielle', de sorte que seul le niveau supérieur de l'objet est dupliqué, et les objets auxquels il fait référence en interne (dans ce cas, la listechildren) sont partagés avec l'originalroot_node. Par conséquent, lorsque vous ajoutez un nouveau nœud àshallow_copy, la listechildrenpartagée est mise à jour et la modification se répercute également sur le contenu deroot_node. - En revanche, la variable
deep_copyest créée via une 'copie profonde', de sorte que la structure interne est dupliquée récursivement, produisant un arbre complètement indépendant deroot_node. Par conséquent, même si vous ajoutez un nouveau nœud àdeep_copy, cela n'affecte pasroot_node.
Références cycliques (objets récursifs) et importance de memo
Lorsqu’un graphe d’objets complexe se référence lui‑même (références cycliques), deepcopy utilise un dictionnaire memo pour suivre les objets déjà copiés et éviter les boucles infinies.
1# Example: recursive list and deepcopy memo demonstration
2import copy
3
4a = []
5b = [a]
6a.append(b) # a -> [b], b -> [a] (cycle)
7
8# deepcopy can handle cycles
9deep = copy.deepcopy(a)
10print("deep copy succeeded, length:", len(deep))- En interne,
deepcopyutilisememopour référencer les objets déjà dupliqués, ce qui permet une copie sûre même en présence de cycles. Vous avez besoin d’un mécanisme similaire lorsque vous effectuez la récursion manuellement.
copyreg et une copie personnalisée de type sérialisation (avancé)
Si vous souhaitez enregistrer un comportement de copie spécial en coordination avec des bibliothèques, vous pouvez utiliser le module standard copyreg pour enregistrer la façon dont les objets sont (re)construits. C’est utile pour les objets complexes et les types d’extensions C.
1# Example: using copyreg to customize pickling/copy behavior (brief example)
2import copy
3import copyreg
4
5class Wrapper:
6 def __init__(self, data):
7 self.data = data
8
9def reduce_wrapper(obj):
10 # Return callable and args so object can be reconstructed
11 return (Wrapper, (obj.data,))
12
13copyreg.pickle(Wrapper, reduce_wrapper)
14
15w = Wrapper([1, 2, 3])
16w_copy = copy.deepcopy(w)
17print("w_copy.data:", w_copy.data)- En utilisant
copyreg, vous pouvez fournir des règles de reconstruction qui affectent à la foispickleetdeepcopy. C’est une API avancée, et dans la plupart des cas__deepcopy__suffit.
Mises en garde et pièges pratiques
Il existe plusieurs points importants pour garantir un comportement correct lors de l’utilisation du module copy. Nous expliquons ci‑dessous les pièges courants que vous pouvez rencontrer en développement et comment les éviter.
- Performances
deepcopypeut consommer beaucoup de mémoire et de CPU ; réfléchissez donc soigneusement à sa nécessité. - Gestion des éléments que vous souhaitez partager
Si vous voulez que certains attributs, comme de gros caches, restent partagés, évitez intentionnellement de copier leurs références dans
__deepcopy__. - État interne immuable Si vous conservez des données immuables en interne, la copie peut être inutile.
- Threads et ressources externes Les ressources qui ne peuvent pas être copiées, comme les sockets et les descripteurs de fichier, sont soit dénuées de sens à copier, soit sources d’erreurs ; il faut donc éviter de les copier par conception.
Exemple pratique : un modèle pour mettre à jour des dictionnaires en toute sécurité
Lors de la mise à jour d’un objet de configuration complexe, cet exemple utilise deepcopy pour protéger les paramètres d’origine.
1# Example: safely update a nested configuration using deepcopy
2import copy
3
4default_config = {
5 "db": {"host": "localhost", "ports": [5432]},
6 "features": {"use_cache": True, "cache_sizes": [128, 256]},
7}
8
9# Create a working copy to modify without touching defaults
10working = copy.deepcopy(default_config)
11working["db"]["ports"].append(5433)
12working["features"]["cache_sizes"][0] = 512
13
14print("default_config:", default_config)
15print("working:", working)En utilisant deepcopy, vous pouvez créer en toute sécurité des configurations dérivées sans risquer d’altérer les paramètres par défaut. C’est particulièrement utile pour les structures mutables imbriquées, telles que les configurations.
Bonnes pratiques
Pour utiliser le module copy en toute sécurité et efficacement, il est important de garder à l'esprit les recommandations pratiques suivantes.
- Choisissez entre copie superficielle et copie profonde en fonction de la probabilité de modifications et de leur portée.
- Implémentez
__copy__et__deepcopy__dans les classes personnalisées pour obtenir le comportement attendu. - Les copies profondes étant coûteuses, réduisez le besoin de copie par la conception lorsque c’est possible. Envisagez l’immuabilité et des méthodes de clonage explicites, entre autres techniques.
- Lorsqu’il s’agit de références cycliques, utilisez
deepcopyou fournissez manuellement un mécanisme similaire àmemo. - Concevez de sorte que les ressources externes, telles que les descripteurs de fichiers et les threads, ne soient pas copiées.
Conclusion
Le module copy est l’outil standard pour la duplication d’objets en Python. En comprenant correctement les différences entre copies superficielles et profondes et en implémentant un comportement de copie personnalisé lorsque c’est nécessaire, vous pouvez dupliquer de manière sûre et prévisible. En précisant dès la phase de conception si la copie est réellement nécessaire et ce qui doit être partagé, vous pouvez éviter des bogues et des problèmes de performances inutiles.
Vous pouvez suivre l'article ci-dessus avec Visual Studio Code sur notre chaîne YouTube. Veuillez également consulter la chaîne YouTube.