[Tutor] Problem with copy

Danny Yoo dyoo@hkn.eecs.berkeley.edu
Tue, 21 Aug 2001 16:52:22 -0700 (PDT)


On Tue, 21 Aug 2001, Bill Ferreira wrote:

> "copy" is not making a copy until I change the source value so I guess I'm 
> doing something wrong, any suggestions?
> 
>  >>> import copy
>  >>> x=1
>  >>> ix=copy.copy(x)
>  >>> ix is x
> 1
> 
> My understanding is that the purpose of copy is to insure that the target 
> object (ix in this case) is a different object than the source object (x). 

[Note: this question is fraught with trickiness.]


Try the copy.deepcopy() function instead: it does what you're thinking of.  
copy.copy() does a shallow copy, which only copies the toplevel part of
the structure.  It's shallow because the inner structure is still shared.

The distinction between shallow copying and deep copying only comes up in
nested structures, like lists within lists.  What you're running into is
something slightly different.  Let's look:



> If I follow up the above code with the following:
> 
>  >>> x = 2
>  >>> x is ix
> 0
> 
> It certainly seems to me that the "is" operator should have returned 0 in 
> both cases. And now:


Try a larger number greater than 101.  Python will automatically cache
integers less that or equal to 101 to save space:

###
>>> for i in range(150):
...     x1 = i + 0
...     x2 = i + 0
...     if x1 is not x2:
...         print "Hey, no more cache starting from", x1
...         break
...
Hey, no more cache starting from 100
###

My mistake: it stops caching the numbers at 100.  Here's what I think
Python's doing: on integer addition, it actually does construct a whole
new number.  However, it then checks to see if it's between 0 and 99.  If
so, it abandons that value, and returns one out of the cache.



However, your test with copy.copy() obscures one very tricky point:
copy.copy() already knows that numbers are immutable, and so it can
optimize by not copying at all!  Let's take a look at some of the code in
copy.copy():

###
def copy(x):
    """Shallow copy operation on arbitrary Python objects.
 
    See the module's __doc__ string for more info.
    """
 
    try:
        copierfunction = _copy_dispatch[type(x)]
    ...
###

Here's an example of dispatch-based programming based on type.  We know
that we're copying integers, so let's take a look at the copier for
integers:

###
                              # slightly edited for presentation
def _copy_atomic(x):
    return x
d[types.IntType] = _copy_atomic
###




That is, copy.copy() doesn't do anything if it sees an "atomic" immutable
type like an integer.  We can test this empirically:

###
>>> for i in range(150):
...     x = copy.copy(i)
...     if i is not x:
...         print "Hey, no more caching behavior, starting from", x
...         break
...
>>>                                      # Hey, nothing happened!
###


That was very very tricky.  *grin*


Hope this helps!