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 ?

3voto

BoltzmannBrain Points 1970

Sur la base de la réponse claire d'Antony Hatchkins, voici ma version où la classe en question dérive d'une autre classe personnalisée (par exemple, nous avons besoin d'appeler super ) :

class Foo(FooBase):
    def __init__(self, param1, param2):
        self._base_params = [param1, param2]
        super(Foo, result).__init__(*self._base_params)

    def __copy__(self):
        cls = self.__class__
        result = cls.__new__(cls)
        result.__dict__.update(self.__dict__)
        super(Foo, result).__init__(*self._base_params)
        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, copy.deepcopy(v, memo))
        super(Foo, result).__init__(*self._base_params)
        return result

3voto

ankostis Points 121

Les copy utilise éventuellement le module __getstate__() / __setstate__() protocole de décapage Il s'agit donc également d'objectifs valables à remplacer.

L'implémentation par défaut se contente de renvoyer et de définir le __dict__ de la classe, de sorte que vous n'avez pas besoin d'appeler super() et s'inquiéter de l'astuce d'Eino Gourdin, ci-dessus .

2voto

Zach Price Points 11

Pierre et Eino Gourdin Les réponses d'Erik sont intelligentes et utiles, mais elles comportent un bogue très subtil !

Les méthodes Python sont liées à leur objet. Lorsque vous faites cp.__deepcopy__ = deepcopy_method vous donnez en fait à l'objet cp une référence à __deepcopy__ sur l'objet original . Tout appel à cp.__deepcopy__ renverra un copie de l'original ! Si vous copiez votre objet en profondeur, puis deepcopy that copy , le résultat est un PAS une copie de la copie !

Voici un exemple minimal de ce comportement, ainsi que mon implémentation fixe où l'on copie le fichier __deepcopy__ puis de le lier au nouvel objet :

from copy import deepcopy
import types

class Good:
    def __init__(self):
        self.i = 0

    def __deepcopy__(self, memo):
        deepcopy_method = self.__deepcopy__
        self.__deepcopy__ = None
        cp = deepcopy(self, memo)
        self.__deepcopy__ = deepcopy_method
        # Copy the function object
        func = types.FunctionType(
            deepcopy_method.__code__,
            deepcopy_method.__globals__,
            deepcopy_method.__name__,
            deepcopy_method.__defaults__,
            deepcopy_method.__closure__,
        )
        # Bind to cp and set
        bound_method = func.__get__(cp, cp.__class__)
        cp.__deepcopy__ = bound_method

        return cp

class Bad:
    def __init__(self):
        self.i = 0

    def __deepcopy__(self, memo):
        deepcopy_method = self.__deepcopy__
        self.__deepcopy__ = None
        cp = deepcopy(self, memo)
        self.__deepcopy__ = deepcopy_method
        cp.__deepcopy__ = deepcopy_method
        return cp

x = Bad()
copy = deepcopy(x)
copy.i = 1
copy_of_copy = deepcopy(copy)
print(copy_of_copy.i)  # 0

x = Good()
copy = deepcopy(x)
copy.i = 1
copy_of_copy = deepcopy(copy)
print(copy_of_copy.i)  # 1

1voto

eltings Points 35

Je suis venu ici pour des raisons de performance. L'utilisation de l'option par défaut copy.deepcopy() ralentissait mon code jusqu'à 30 fois. En utilisant la fonction répondre par @ Anthony Hatchkins comme point de départ, j'ai réalisé que copy.deepcopy() est vraiment lent pour les listes par exemple. J'ai remplacé le setattr avec un simple [:] slicing pour copier des listes entières. Pour toute personne soucieuse des performances, il vaut la peine de faire timeit.timeit() et en remplaçant les appels à copy.deepcopy() par des solutions plus rapides.

setup = 'import copy; l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]'
timeit.timeit(setup = setup, stmt='m=l[:]')
timeit.timeit(setup = setup, stmt='m=l.copy()')
timeit.timeit(setup = setup, stmt='m=copy.deepcopy(l)')

donnera ces résultats :

0.11505379999289289
0.09126630000537261
6.423627900003339

0voto

NeverMore Points 11

Semblable à Zach Price il existe un moyen plus simple d'atteindre cet objectif, c'est-à-dire de délier l'objet original du __deepcopy__ puis la lier à la méthode cp

from copy import deepcopy
import types

class Good:
    def __init__(self):
        self.i = 0

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

        # Bind to cp by types.MethodType
        cp.__deepcopy__ = types.MethodType(deepcopy_method.__func__, cp)

        return cp

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