web-dev-qa-db-fra.com

Comment 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 avoir des objets indépendants. Donc, si je change les valeurs des champs du nouvel objet, cela n’affectera pas l’ancien objet.

154
Roman

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

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

148
Sven Marnach

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

Donc, si je change les valeurs des champs du nouvel objet, cela n’affectera pas l’ancien objet.

Vous voulez dire un objet mutable alors.

Dans Python 3, les listes obtiennent une méthode copy (dans 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 peu profondes

Les copies superficielles ne sont que des copies du conteneur le plus à l'extérieur.

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. Ils sont le même objet - donc, quand ils sont mutés, le changement 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, 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 n-uplets n'ont même pas de méthode de copie, alors essayons-le avec une tranche:

>>> 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 chaînes:

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

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

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

Quand copier des objets immuables

Les objets immuables devraient ​​être copiés 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 constater, lorsque l'objet intérieur de la copie est muté, l'original change not.

Objets Personnalisés

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

Pour créer un objet pouvant être copié, 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émoization de id(original) (ou numéros d’identité) sur les copies. Pour profiter du bon comportement avec les structures de données récursives, assurez-vous de ne pas avoir déjà fait de copie et, le cas échéant, renvoyez-le.

Alors faisons un objet:

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

Et copy en 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 en profondeur:

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

Copie peu profonde 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.