tags:

views:

350

answers:

2

So just to establish, I feel like I understand the difference between copy vs. deepcopy in the copy module and I've used copy.copy and copy.deepcopy before successfully, but this is the first time I've actually gone about overloading the __copy__ and __deepcopy__ methods. I've already Googled around and looked through the built-in Python modules to look for instances of the __copy__ and __deepcopy__ functions (e.g. sets.py, decimal.py, and fractions.py), but I'm still not 100% sure I've got it right.

Here's my scenario:

I have a configuration object that mostly just consists of simple properties (though it will potentially have lists of other non-primitive objects in it). Initially I'm going to instantiate one configuration object with a default set of values. This configuration will be handed off to multiple other objects (to ensure all objects start with the same configuration). Once user interaction starts, however, each object will need to be able to tweak the configurations independently without affecting each other's configurations (which says to me I'll need to make deepcopys of my initial configuration to hand around).

Here's a sample object:

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

What is the right way to implement the copy and deepcopy methods on this object to ensure copy.copy and copy.deepcopy give me the proper behavior? I'm currently using Python 2.6.2.

Thanks in advance!

+1  A: 

I might be a bit of on the specifics, but here goes;

From the copy docs;

  • A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original.
  • A deep copy constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original.

In other words: copy() will copy only the top element and leave the rest as pointers into the original structure. deepcopy() will recursively copy over everything.

Alas, deepcopy() is what you need.

If you need to do something really specific, you can override __copy__() or __deepcopy__(), as described in the manual. Personally, I'd probably implement a plain function (e.g. config.copy_config() or such) to make it plain that it isn't Python standard behaviour.

Morten Siebuhr
*In order for a class to define its own copy implementation, it can define special methods `__copy__(`) and `__deepcopy__()`.* http://docs.python.org/library/copy.html
SilentGhost
I'll double-check my code, thanks. I'm going to feel dumb if this was a simple bug elsewhere :-P
Brent Nash
@MortenSiebuhr You are correct. I wasn't entirely clear that copy/deepcopy would do anything by default without me overriding those functions. I was looking for actual code though that I can tweak later (e.g. if I don't want to copy all attributes), so I gave you an up-vote but I'm going to go with @AlexMartinelli's answer. Thanks!
Brent Nash
Ah! self.RTFM()... I've fixed the response to show match.
Morten Siebuhr
+4  A: 

The recommendations for customizing are at the very end of the docs page:

Classes can use the same interfaces to control copying that they use to control pickling. See the description of module pickle for information on these methods. The copy module does not use the copy_reg registration module.

In order for a class to define its own copy implementation, it can define special methods __copy__() and __deepcopy__(). The former is called to implement the shallow copy operation; no additional arguments are passed. The latter is called to implement the deep copy operation; it is passed one argument, the memo dictionary. If the __deepcopy__() implementation needs to make a deep copy of a component, it should call the deepcopy() function with the component as first argument and the memo dictionary as second argument.

Since you appear not to care about pickling customization, defining __copy__ and __deepcopy__ definitely seems like the right way to go for you.

Specifically, __copy__ (the shallow copy) is pretty easy in your case...:

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

__deepcopy__ would be similar (accepting a memo arg too) but before the return it would have to call self.foo = deepcopy(self.foo, memo) for any attribute self.foo that needs deep copying (essentially attributes that are containers -- lists, dicts, non-primitive objects which hold other stuff through their __dict__s).

Alex Martelli
I think I have heard that it's better to override `__getstate__`/`__setstate__` to implement copying. Or am I confused?
kaizer.se
@kaizer, they're fine to customize pickling/unpickling as well as copying, but if you don't care about the pickling, it's simpler and more direct to use `__copy__`/`__deepcopy__`.
Alex Martelli
Thanks for the explanation!
Brent Nash
@Brent, you're welcome!
Alex Martelli