Python >> Tutoriel Python >  >> Python

Comment remplacer les opérations de copie/deepcopy pour un objet Python ?

En rassemblant la réponse d'Alex Martelli et le commentaire de Rob Young, vous obtenez le code suivant :

from copy import copy, deepcopy

class A(object):
    def __init__(self):
        print 'init'
        self.v = 10
        self.z = [2,3,4]

    def __copy__(self):
        cls = self.__class__
        result = cls.__new__(cls)
        result.__dict__.update(self.__dict__)
        return result

    def __deepcopy__(self, memo):
        cls = self.__class__
        result = cls.__new__(cls)
        memo[id(self)] = result
        for k, v in self.__dict__.items():
            setattr(result, k, deepcopy(v, memo))
        return result

a = A()
a.v = 11
b1, b2 = copy(a), deepcopy(a)
a.v = 12
a.z.append(5)
print b1.v, b1.z
print b2.v, b2.z

impressions

init
11 [2, 3, 4, 5]
11 [2, 3, 4]

ici __deepcopy__ remplit le memo dict pour éviter une copie excessive au cas où l'objet lui-même serait référencé à partir de son membre.


Les recommandations de personnalisation se trouvent à la toute fin de la page de documentation :

Les classes peuvent utiliser les mêmes interfaces pour contrôler la copie qu'elles utilisent pour contrôler le décapage. Voir la description du module pickle pour des informations sur ces méthodes. Le module de copie n'utilise pas le module d'enregistrement copy_reg.

Pour qu'une classe définisse sa propre implémentation de copie, elle peut définir des méthodes spéciales __copy__() et__deepcopy__() . Le premier est appelé pour implémenter l'opération de copie superficielle; aucun argument supplémentaire n'est passé. Ce dernier est appelé à mettre en œuvre l'opération de copie profonde; il a passé un argument, le memodictionnaire. Si le __deepcopy__() l'implémentation doit faire une copie profonde d'un composant, elle doit appeler le deepcopy() fonction avec le composant comme premier argument et le dictionnaire thememo comme second argument.

Puisque vous ne semblez pas vous soucier de la personnalisation du décapage, définissez __copy__ et __deepcopy__ semble définitivement être la bonne voie à suivre pour vous.

Plus précisément, __copy__ (la copie superficielle) est assez facile dans votre cas... :

def __copy__(self):
  newone = type(self)()
  newone.__dict__.update(self.__dict__)
  return newone

__deepcopy__ serait similaire (accepter un memo arg aussi) mais avant le retour il faudrait appeler self.foo = deepcopy(self.foo, memo) pour tout attribut self.foo qui nécessite une copie en profondeur (essentiellement des attributs qui sont des conteneurs - des listes, des dicts, des objets non primitifs qui contiennent d'autres éléments via leur __dict__ s).


Suite à l'excellente réponse de Peter, pour implémenter une copie profonde personnalisée, avec une modification minimale de l'implémentation par défaut (par exemple, en modifiant simplement un champ comme j'en avais besoin) :

class Foo(object):
    def __deepcopy__(self, memo):
        deepcopy_method = self.__deepcopy__
        self.__deepcopy__ = None
        cp = deepcopy(self, memo)
        self.__deepcopy__ = deepcopy_method
        cp.__deepcopy__ = deepcopy_method

        # custom treatments
        # for instance: cp.id = None

        return cp