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


200

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, l'ancien objet ne devrait pas être affecté par cela.

Réponses:


180

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

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


2
Cette réponse a été signalée comme "Pas une réponse", supprimée et non supprimée - méta-discussion ici: meta.stackoverflow.com/questions/377844/…
Aaron Hall

@AaronHall Merci de m'avoir prévenu! Ce n'est certainement pas la meilleure réponse que j'ai écrite, mais je suis en quelque sorte d'accord avec la décision de ne pas la supprimer de force. Je vais le brosser un peu, mais comme il y a déjà des réponses avec tous les détails (notamment le vôtre), je serai bref.
Sven Marnach

70

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

Donc, si je change les valeurs des champs du nouvel objet, l'ancien objet ne devrait pas être affecté par cela.

Vous voulez dire un objet mutable alors.

En Python 3, les listes obtiennent une copyméthode (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 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'avez pas de copie des objets intérieurs. Ils sont le même objet - donc lorsqu'ils sont mutés, le changement apparaît dans les deux conteneurs.

Copies complètes

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, 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 ne vous donnera que l'objet d'origine:

>>> 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-le avec une tranche:

>>> tuple_copy_attempt = a_tuple[:]

Mais nous voyons 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 les frozensets, même s'ils ont une copyméthode:

>>> 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 doivent être copiés si vous avez besoin d'un objet intérieur mutable copié.

>>> 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 change pas .

Objets personnalisés

Les objets personnalisés stockent généralement des données dans un __dict__attribut ou dans __slots__(une structure de mémoire de type tuple).

Pour créer un objet copiable, définissez __copy__(pour les copies superficielles) et / ou __deepcopy__(pour les copies complètes ).

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 deepcopyconserve un dictionnaire de mémorisation id(original)(ou numéros d'identité) pour les copies. Pour profiter d'un bon comportement avec des structures de données récursives, assurez-vous que vous n'en avez pas déjà fait une copie et, si vous en avez, renvoyez-la.

Faisons donc un objet:

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

Et copyfait une copie superficielle:

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

Et deepcopyfait maintenant une copie complète:

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

11

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 complète 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.


-2

Je pense que ce qui suit devrait fonctionner avec de nombreux classeurs bien comportés en Python:

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

(Bien sûr, je ne parle pas ici de "copies profondes", qui est une histoire différente, et qui n'est peut-être pas un concept très clair - à quelle profondeur est-elle suffisamment profonde?)

Selon mes tests avec Python 3, pour les objets immuables, comme les tuples ou les chaînes, il renvoie le même objet (car il n'est pas nécessaire 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 de conteneur Python standard.


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

@JaredSmith, il n'a pas été précisé que la question concernait tous les objets. Il n'était même pas clair s'il s'agissait d'une copie profonde ou superficielle (je suppose une copie superficielle habituelle, mais la réponse acceptée concerne une copie profonde). Quant aux classes personnalisées, si elles sont à vous, vous pouvez simplement respecter ce type de convention dans leur __init__méthode. Donc, j'ai pensé que cette méthode pouvait être assez bonne pour certains objectifs. Dans tous les cas, je serai intéressé par des commentaires informatifs sur cette suggestion.
Alexey

Considérez class Foo(object): def __init__(self, arg): super(Foo, self).__init__() self.arg = argBasic comme il se présente. Si je le fais, cela foo = Foo(3) bar = copy(foo) print(foo.arg) # 3 print(bar.arg) # <__main__.Foo object at ...>signifie que votre copyfonction est interrompue, même pour les classes les plus élémentaires. Encore une fois, c'est une astuce intéressante (donc pas de DV), mais pas une réponse.
Jared Smith

@JaredSmith, j'ai vu qu'il existe une copy.copyméthode pour faire des copies superficielles, mais, peut-être naïvement, il me semble que ce devrait être la responsabilité de la classe de fournir un "constructeur de copie superficielle". Dans ce cas, pourquoi ne pas lui fournir la même interface que dictet le listfaire? Donc, si votre classe veut prendre la responsabilité de copier ses objets, pourquoi ne pas y ajouter une if isinstance(arg, type(self))clause __init__?
Alexey

1
Parce que vous n'avez pas toujours le contrôle sur les classes que vous utilisez comme vous les définissez. Il peut s'agir, à titre d'exemple, de programmes C dotés de liaisons Python (par exemple GTK, openalpr, parties de core). Sans oublier que même si vous avez pris une bibliothèque tierce et ajouté des méthodes de copie à chaque classe, comment allez-vous intégrer cela dans votre gestion des dépendances?
Jared Smith
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.