364 votes

Comment puis-je créer une copie d'un objet en Python ?

Je voudrais créer une copie d'un objet. Je veux que le nouvel objet possède toutes les propriétés de l'ancien objet (valeurs des champs). Mais je veux que les objets soient indépendants. Ainsi, si je modifie les valeurs des champs du nouvel objet, l'ancien objet ne doit pas en être affecté.

311voto

Sven Marnach Points 133943

Pour obtenir une copie totalement indépendante d'un objet, vous pouvez utiliser la fonction copy.deepcopy() fonction.

Pour plus de détails sur la copie superficielle et la copie profonde, veuillez vous référer aux autres réponses à cette question et à la belle explication dans cette réponse à une question connexe .

2 votes

Cette réponse a été signalée comme "Pas une réponse", supprimée, puis rétablie - méta-discussion ici : meta.stackoverflow.com/questions/377844/

0 votes

@AaronHall Merci de me le faire savoir ! Ce n'est certainement pas la meilleure réponse que j'ai écrite, mais je suis assez d'accord avec la décision de ne pas la supprimer de force. Je vais la retravailler un peu, mais comme il y a déjà des réponses avec tous les détails (notamment la vôtre), je vais la garder courte.

155voto

Aaron Hall Points 7381

Comment puis-je créer une copie d'un objet en Python ?

Ainsi, si je modifie les valeurs des champs du nouvel objet, l'ancien objet ne devrait pas en être affecté.

Vous voulez dire un objet mutable alors.

En Python 3, les listes obtiennent un copy (en 2, vous utiliseriez une tranche pour faire une copie) :

>>> a_list = list('abc')
>>> a_copy_of_a_list = a_list.copy()
>>> a_copy_of_a_list is a_list
False
>>> a_copy_of_a_list == a_list
True

Copies superficielles

Les copies superficielles ne sont que des copies du conteneur le plus externe.

list.copy est une copie superficielle :

>>> list_of_dict_of_set = [{'foo': set('abc')}]
>>> lodos_copy = list_of_dict_of_set.copy()
>>> lodos_copy[0]['foo'].pop()
'c'
>>> lodos_copy
[{'foo': {'b', 'a'}}]
>>> list_of_dict_of_set
[{'foo': {'b', 'a'}}]

Vous n'obtenez pas une copie des objets intérieurs. Il s'agit du même objet. Ainsi, lorsqu'ils sont mutés, la modification apparaît dans les deux conteneurs.

Copies profondes

Les copies profondes sont des copies récursives de chaque objet intérieur.

>>> lodos_deep_copy = copy.deepcopy(list_of_dict_of_set)
>>> lodos_deep_copy[0]['foo'].add('c')
>>> lodos_deep_copy
[{'foo': {'c', 'b', 'a'}}]
>>> list_of_dict_of_set
[{'foo': {'b', 'a'}}]

Les modifications ne sont pas reflétées dans l'original, mais uniquement dans la copie.

Objets immuables

Les objets immuables n'ont généralement pas besoin d'être copiés. En fait, si vous essayez de le faire, Python vous donnera simplement l'objet original :

>>> a_tuple = tuple('abc')
>>> tuple_copy_attempt = a_tuple.copy()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'tuple' object has no attribute 'copy'

Les tuples n'ont même pas de méthode de copie, alors essayons avec un slice :

>>> tuple_copy_attempt = a_tuple[:]

Mais on voit que c'est le même objet :

>>> tuple_copy_attempt is a_tuple
True

De même pour les cordes :

>>> s = 'abc'
>>> s0 = s[:]
>>> s == s0
True
>>> s is s0
True

et pour les frozensets, même s'ils ont une copy méthode :

>>> a_frozenset = frozenset('abc')
>>> frozenset_copy_attempt = a_frozenset.copy()
>>> frozenset_copy_attempt is a_frozenset
True

Quand copier des objets immuables

Objets immuables devrait être copié si vous avez besoin de copier un objet intérieur mutable.

>>> tuple_of_list = [],
>>> copy_of_tuple_of_list = tuple_of_list[:]
>>> copy_of_tuple_of_list[0].append('a')
>>> copy_of_tuple_of_list
(['a'],)
>>> tuple_of_list
(['a'],)
>>> deepcopy_of_tuple_of_list = copy.deepcopy(tuple_of_list)
>>> deepcopy_of_tuple_of_list[0].append('b')
>>> deepcopy_of_tuple_of_list
(['a', 'b'],)
>>> tuple_of_list
(['a'],)

Comme nous pouvons le voir, lorsque l'objet intérieur de la copie est muté, l'original ne le fait pas. no changement.

Objets personnalisés

Les objets personnalisés stockent généralement les données dans un __dict__ ou dans l'attribut __slots__ (une structure de mémoire de type tuple).

Pour faire un objet copiable, définissez __copy__ (pour les copies superficielles) et/ou __deepcopy__ (pour les copies profondes).

from copy import copy, deepcopy

class Copyable:
    __slots__ = 'a', '__dict__'
    def __init__(self, a, b):
        self.a, self.b = a, b
    def __copy__(self):
        return type(self)(self.a, self.b)
    def __deepcopy__(self, memo): # memo is a dict of id's to copies
        id_self = id(self)        # memoization avoids unnecesary recursion
        _copy = memo.get(id_self)
        if _copy is None:
            _copy = type(self)(
                deepcopy(self.a, memo), 
                deepcopy(self.b, memo))
            memo[id_self] = _copy 
        return _copy

Notez que deepcopy conserve un dictionnaire de mémorisation de id(original) (ou numéros d'identité) aux copies. Pour profiter d'un bon comportement avec les structures de données récursives, assurez-vous que vous n'avez pas déjà fait une copie, et si c'est le cas, renvoyez-la.

Donc, faisons un objet :

>>> c1 = Copyable(1, [2])

Et copy fait une copie superficielle :

>>> c2 = copy(c1)
>>> c1 is c2
False
>>> c2.b.append(3)
>>> c1.b
[2, 3]

Et deepcopy fait maintenant une copie profonde :

>>> c3 = deepcopy(c1)
>>> c3.b.append(4)
>>> c1.b
[2, 3]

25voto

Ciro Santilli Points 3341

Copie superficielle avec copy.copy()

#!/usr/bin/env python3

import copy

class C():
    def __init__(self):
        self.x = [1]
        self.y = [2]

# It copies.
c = C()
d = copy.copy(c)
d.x = [3]
assert c.x == [1]
assert d.x == [3]

# It's shallow.
c = C()
d = copy.copy(c)
d.x[0] = 3
assert c.x == [3]
assert d.x == [3]

Copie profonde avec copy.deepcopy()

#!/usr/bin/env python3
import copy
class C():
    def __init__(self):
        self.x = [1]
        self.y = [2]
c = C()
d = copy.deepcopy(c)
d.x[0] = 3
assert c.x == [1]
assert d.x == [3]

Documentation : https://docs.python.org/3/library/copy.html

Testé sur Python 3.6.5.

-1voto

Alexey Points 562

Je pense que ce qui suit devrait fonctionner avec de nombreuses classes bien conduites en Python :

def copy(obj):
    return type(obj)(obj)

(Bien sûr, je ne parle pas ici des "copies profondes", ce qui est une autre histoire, et qui n'est peut-être pas un concept très clair - jusqu'à quel point la profondeur est-elle suffisante ?)

D'après mes tests avec Python 3, pour les objets immuables, comme les tuples ou les chaînes de caractères, il retourne le même objet (car il n'y a pas besoin de faire une copie superficielle d'un objet immuable), mais pour les listes ou les dictionnaires, il crée une copie superficielle indépendante.

Bien sûr, cette méthode ne fonctionne que pour les classes dont les constructeurs se comportent en conséquence. Cas d'utilisation possibles : faire une copie superficielle d'une classe conteneur standard Python.

0 votes

C'est bien et tout, mais cela ne répond pas à la question car votre fonction de copie échoue pour les classes personnalisées et la question portait sur objets .

0 votes

@JaredSmith, il n'a pas été dit que la question portait sur todo des objets. Il n'était même pas clair s'il s'agissait de la copie profonde ou superficielle (j'aurais supposé la copie superficielle habituelle, mais la réponse acceptée est la copie profonde). En ce qui concerne les classes personnalisées, si elles sont les vôtres, vous pouvez simplement respecter ce type de convention dans leurs classes __init__ méthode. J'ai donc pensé que cette méthode pourrait être suffisante pour certains objectifs. En tout cas, je serai intéressé par les commentaires informatifs sur cette suggestion.

1 votes

Pensez à class Foo(object): def __init__(self, arg): super(Foo, self).__init__() self.arg = arg Aussi basique que possible. Si je fais foo = Foo(3) bar = copy(foo) print(foo.arg) # 3 print(bar.arg) # <__main__.Foo object at ...> Ce qui signifie que votre copy est cassée, même pour les classes les plus basiques. Encore une fois, c'est une astuce soignée (d'où l'absence de DV), mais pas une réponse.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X