5.12 Making a Fast Copy of an Object


Credit: Alex Martelli

5.12.1 Problem

You need to implement the special method _ _copy_ _ so your class can cooperate with the copy.copy function. If the _ _init_ _ method of your class is slow, you need to bypass it and get an empty object of the class.

5.12.2 Solution

Here's a solution that works for both new-style and classic classes:

def empty_copy(obj):     class Empty(obj._ _class_ _):         def _ _init_ _(self): pass     newcopy = Empty(  )     newcopy._ _class_ _ = obj._ _class_ _     return newcopy

Your classes can use this function to implement _ _copy_ _ as follows:

class YourClass:     def _ _init_ _(self):         print "assume there's a lot of work here"     def _ _copy_ _(self):         newcopy = empty_copy(self)         print "now copy some relevant subset of self's attributes to newcopy"         return newcopy

Here's a usage example:

if _ _name_ _ == '_ _main_ _':     import copy     y = YourClass(  )    # This, of course, does run _ _init_ _     print y     z = copy.copy(y)     # ...but this doesn't     print z

5.12.3 Discussion

Python doesn't implicitly copy your objects when you assign them. This is a great thing, because it gives fast, flexible, and uniform semantics. When you need a copy, you explicitly ask for it, ideally with the copy.copy function, which knows how to copy built-in types, has reasonable defaults for your own objects, and lets you customize the copying process by defining a special method _ _copy_ _ in your own classes. If you want instances of a class to be noncopyable, you can define _ _copy_ _ and raise a TypeError there. In most cases, you can let copy.copy's default mechanism work, and you get free clonability for most of your classes. This is quite a bit nicer than languages that force you to implement a specific clone method for every class whose instances you want to be clonable.

_ _copy_ _ often needs to start with an empty instance of the class in question (e.g., self), bypassing _ _init_ _ when that is a costly operation. The simplest way to do this is to use the ability that Python gives you to change an instance's class on the fly by creating a new object in a local empty class, then setting its _ _class_ _ attribute, as the recipe's code shows. Note that inheriting class Empty from obj._ _class_ _ is redundant (but quite innocuous) for old Python versions (up to Python 2.1), but in Python 2.2 it becomes necessary to make the empty_copy function compatible with all kinds of objects of classic or new-style classes (including built-in and extension types). Once you choose to inherit from obj's class, you must override _ _init_ _ in class Empty, or else the whole purpose of the recipe is lost.

Once you have an empty object of the required class, you typically need to copy a subset of self's attributes. If you need all of the attributes, you're better off not defining _ _copy_ _ explicitly, since copying all instance attributes is copy.copy's default. Unless, of course, you should need to do a little bit more than copying instance attributes. If you do need to copy all of self's attributes into newcopy, here are two techniques:

newcopy._ _dict_ _.update(self._ _dict_ _) newcopy._ _dict_ _ = self._ _dict_ _.copy(  )

An instance of a new-style class doesn't necessarily keep all of its state in _ _dict_ _, so you may need to do some class-specific state copying.

Alternatives based on the new standard module can't be made transparent across classic and new-style classes in Python 2.2 (at least, I've been unable to do this). Besides, the new module is often thought of as dangerous black magic (rather exaggerating its dangers). Anyway, this recipe lets you avoid using the new module for this specific purpose.

Note that so far we have been talking about shallow copies, which is what you want most of the time. With a shallow copy, your object is copied, but objects it refers to (attributes or items) are not, so the new copied object and the original object refer to the same items or attributes objects. A deep copy is a heavyweight operation, potentially duplicating a large graph of objects that refer to each other. You get a deep copy by calling copy.deepcopy on an object. If you need to customize how instances of your class are deep-copied, you can define the special method _ _deepcopy_ _ and follow its somewhat complicated memoization protocol. The technique shown in this recipe getting empty copies of objects by bypassing their _ _init_ _ methods can sometimes still come in handy, but there is a lot of other work you need to do.

5.12.4 See Also

The Library Reference section on the copy module.



Python Cookbook
Python Cookbook
ISBN: 0596007973
EAN: 2147483647
Year: 2005
Pages: 346

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net