140 votes

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

Je comprends la différence entre copy vs. deepcopy dans le module de copie. J'ai utilisé copy.copy y copy.deepcopy auparavant avec succès, mais c'est la première fois que j'ai réellement surchargé la fonction __copy__ y __deepcopy__ des méthodes. J'ai déjà cherché sur Google et dans les modules Python intégrés des instances de la méthode __copy__ y __deepcopy__ (par exemple sets.py , decimal.py y fractions.py ), mais je ne suis pas encore sûr à 100 % d'avoir bien compris.

Voici mon scénario :

J'ai un objet de configuration. Au départ, je vais instancier un objet de configuration avec un ensemble de valeurs par défaut. Cette configuration sera transmise à plusieurs autres objets (pour s'assurer que tous les objets démarrent avec la même configuration). Cependant, une fois que l'interaction avec l'utilisateur commence, chaque objet doit modifier ses configurations indépendamment sans affecter les configurations des autres (ce qui me fait dire que je vais devoir faire des copies intégrales de ma configuration initiale pour la distribuer).

Voici un exemple d'objet :

class ChartConfig(object):

    def __init__(self):

        #Drawing properties (Booleans/strings)
        self.antialiased = None
        self.plot_style = None
        self.plot_title = None
        self.autoscale = None

        #X axis properties (strings/ints)
        self.xaxis_title = None
        self.xaxis_tick_rotation = None
        self.xaxis_tick_align = None

        #Y axis properties (strings/ints)
        self.yaxis_title = None
        self.yaxis_tick_rotation = None
        self.yaxis_tick_align = None

        #A list of non-primitive objects
        self.trace_configs = []

    def __copy__(self):
        pass

    def __deepcopy__(self, memo):
        pass 

Quelle est la bonne façon de mettre en œuvre le copy y deepcopy sur cet objet pour s'assurer que copy.copy y copy.deepcopy me donner le comportement adéquat ?

134voto

Antony Hatchkins Points 5831

En combinant 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

empreintes

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

aquí __deepcopy__ remplit le memo afin d'éviter toute copie excessive dans le cas où l'objet lui-même est référencé à partir de son membre.

93voto

Alex Martelli Points 330805

Les recommandations pour la personnalisation se trouvent à la fin de l'article. page de documentation :

Les classes peuvent utiliser les mêmes dans contrôler la copie qu'elles utilisent pour contrôler le décapage. Voir la description du module pickle pour plus d'informations sur ces méthodes. Le module copy fait [ ] du module d'enregistrement copy_reg.

Pour qu'une classe puisse définir ses propres elle peut définir des des méthodes spéciales __copy__() et __deepcopy__() . Le premier est appelé pour mettre en œuvre l'opération de copie superficielle aucun argument supplémentaire n'est transmis. La seconde est appelée pour mettre en œuvre l'opération de copie profonde ; il un seul argument, le dictionnaire memo un argument, le dictionnaire memo Si le dictionnaire __deepcopy__() La mise en œuvre doit faire l'objet d'une attention particulière. d'un composant, elle doit appeler le deepcopy() f comme premier argument et la fonction comme deuxième argument.

Puisque vous ne semblez pas vous soucier de la personnalisation du décapage, la définition de __copy__ y __deepcopy__ Il semble que ce soit la bonne solution pour vous.

En particulier, __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 (en acceptant un memo arg aussi) mais avant le retour, il devrait 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 -- listes, dicts, objets non primitifs qui contiennent d'autres choses par le biais de leur __dict__ s).

21voto

Eino Gourdin Points 440

Suivant L'excellente réponse de Peter Le logiciel a été conçu pour permettre la mise en œuvre d'un deepcopy personnalisé, 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

Edit : une limitation de cette approche, comme le souligne Igor Kozyrenko, est que les "copies" ne peuvent pas être utilisées pour des raisons de sécurité. __deepcopy__ sera toujours lié à l'objet original, de sorte qu'une copie d'une copie sera en fait une copie de l'original. Il y a peut-être un moyen de lier à nouveau l'objet __deepcopy__ à cp au lieu de l'assigner simplement avec cp.__deepcopy__ = deepcopy_method

12voto

Peter Points 121

La raison pour laquelle vous devez surcharger ces méthodes n'est pas claire dans votre problème, puisque vous ne voulez pas personnaliser les méthodes de copie.

Quoi qu'il en soit, si vous souhaitez personnaliser la copie intégrale (par exemple en partageant certains attributs et en en copiant d'autres), voici une solution :

from copy import deepcopy

def deepcopy_with_sharing(obj, shared_attribute_names, memo=None):
    '''
    Deepcopy an object, except for a given list of attributes, which should
    be shared between the original object and its copy.

    obj is some object
    shared_attribute_names: A list of strings identifying the attributes that
        should be shared between the original and its copy.
    memo is the dictionary passed into __deepcopy__.  Ignore this argument if
        not calling from within __deepcopy__.
    '''
    assert isinstance(shared_attribute_names, (list, tuple))
    shared_attributes = {k: getattr(obj, k) for k in shared_attribute_names}

    if hasattr(obj, '__deepcopy__'):
        # Do hack to prevent infinite recursion in call to deepcopy
        deepcopy_method = obj.__deepcopy__
        obj.__deepcopy__ = None

    for attr in shared_attribute_names:
        del obj.__dict__[attr]

    clone = deepcopy(obj)

    for attr, val in shared_attributes.iteritems():
        setattr(obj, attr, val)
        setattr(clone, attr, val)

    if hasattr(obj, '__deepcopy__'):
        # Undo hack
        obj.__deepcopy__ = deepcopy_method
        del clone.__deepcopy__

    return clone

class A(object):

    def __init__(self):
        self.copy_me = []
        self.share_me = []

    def __deepcopy__(self, memo):
        return deepcopy_with_sharing(self, shared_attribute_names = ['share_me'], memo=memo)

a = A()
b = deepcopy(a)
assert a.copy_me is not b.copy_me
assert a.share_me is b.share_me

c = deepcopy(b)
assert c.copy_me is not b.copy_me
assert c.share_me is b.share_me

7voto

Morten Siebuhr Points 2916

Il se peut que je me trompe un peu sur les détails, mais voici ce qu'il en est ;

A partir de la copy documents ;

  • Une copie superficielle construit un nouvel objet composé et y insère (dans la mesure du possible) des références aux objets présents dans l'original.
  • Une copie profonde construit un nouvel objet composé et y insère, de manière récursive, des copies des objets présents dans l'original.

En d'autres termes : copy() ne copiera que l'élément supérieur et laissera le reste sous forme de pointeurs dans la structure d'origine. deepcopy() copiera tout de manière récursive.

C'est-à-dire, deepcopy() est ce qu'il vous faut.

Si vous avez besoin de faire quelque chose de vraiment spécifique, vous pouvez surcharger __copy__() o __deepcopy__() comme décrit dans le manuel. Personnellement, j'implémenterais probablement une fonction simple (par ex. config.copy_config() ou autre) pour indiquer clairement qu'il ne s'agit pas d'un comportement standard de Python.

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