strange side effect with lists!?

Bengt Richter bokr at oz.net
Wed Oct 13 14:57:34 CEST 2004


On Wed, 13 Oct 2004 14:19:56 +0200, aleaxit at yahoo.com (Alex Martelli) wrote:

><Wolfgang.Stoecher at profactor.at> wrote:
>
>> Hello,
>> 
>> I'm new to Python and playing around. I'm confused by the following 
>> behaviour:
>> 
>> >>> l1 = [1] # define list
>> >>> l2 = l1  # copy of l1 ?
>> >>> l2,l1
>> ([1], [1])
>> >>> l2.extend(l1) # only l2 should be altered !?
>> >>> l2,l1
>> ([1, 1], [1, 1]) # but also l1 is altered!
>> 
>> So what is the policy of assignment? When is a copy of an object created?
>> Where to find dox on this?
>
>Bruno's answer seems very thorough so I'll just try to briefly summarize
>the answers:
>
>1. simple assignment (to a bare name, at least), per se, never
>   implicitly copies objects, but rather it sets a reference to an
>   object (_another_ reference if the object already had some).
>
>2. a new object is created when you request such creation or perform
>   operations that require it.  Lists are particularly rich in such
>   operations (see later).  Simple assignment to a bare name is not
>   an operation, per se -- it only affects the name, by making it refer
>   to whatever object (new, or used;-) is on the righht of the '='.
>
>3. I believe any decent book on Python will cover this in detail.
>
>Now for ways to have a new list object L2 made, with just the same items
>and in the same order as another list object L1 ("shallow copy"):
>
>a. import copy; L2 = copy.copy(L1)
>
>This works to shallow-copy _any_ copyable object L1; unfortunately you
>do have to import copy first.  Module copy also exposes function
>deepcopy, for those rare cases in which you wish to recursively also get
>copies of all objects to which a "root object" refers (as items, or
>attributes; there are some anomalies, e.g., copy.deepcopy(X) is X when X
>is a class, or type...).
>
>b. L2 = list(L1)
>
>I find this is most often what I use - it works (making a new list
>object) whatever kind of sequence, iterator, or other iterable L1 may
>be.  It is also what I recommend you use unless you have some very
>specific need best met otherwise.
>
>c. various operations such as...:
>   L2 = L1 * 1
>   L2 = L1 + []
>   L2 = L1[:]
>i.e. "multiply by one", "concatenate the empty list", or "get a slice of
>all items".  I'm not sure why, but the latter seems to be very popular,
>even though it's neither as concise as L1*1 nor as clear and readable as
>list(L1).
>
I got curious:

 >>> L = range(5)
 >>> L
 [0, 1, 2, 3, 4]

Make it self-referential:
 >>> L[2]=L
 >>> L
 [0, 1, [...], 3, 4]

 >>> import copy
 >>> Lc = copy.copy(L)
 >>> Lc
 [0, 1, [0, 1, [...], 3, 4], 3, 4]

 >>> Ldc = copy.deepcopy(L)
 >>> Ldc
 [0, 1, [...], 3, 4]

Interesting that the deep copy made the copy self-referential
(i.e., to the copy itself) like the original's reference to
its (different) self:

 >>> id(Ldc) == id(Ldc[2])
 True

Unlike the shallow copy:
 >>> id(Lc) == id(Lc[2])
 False
...whose middle reference was merely copied and sill refers to L:
 >>> id(Lc[2]) == id(L)
 True

But like the original:
 >>> id(L) == id(L[2])
 True

I'm impressed ;-)

Regards,
Bengt Richter



More information about the Python-list mailing list