[Tutor] Nested use of replication operator on lists

boB Stepp robertvstepp at gmail.com
Sat May 26 02:10:06 EDT 2018

The subtleties of the interactions between the concepts of references
to objects, mutable objects, immutable objects, shallow copy of
objects and deep copy of objects continue to surprise me!

On Fri, May 25, 2018 at 1:27 AM, Steven D'Aprano <steve at pearwood.info> wrote:
> On Thu, May 24, 2018 at 10:33:56PM -0500, boB Stepp wrote:

> The result of list * 3 is always a list. What matters is the items
> inside the list.
> What the * operator does is create a new list containing the entries of
> the old list repeated.

I was not properly appreciating that that these repeated objects were
the *same identical* objects that were in the pre-replicated list.

> If the items are immutable, like integers, that is perfectly fine.
> Copying an immutable object is a waste of time, and in fact the standard
> copy function will usually refuse to do so:
> py> import copy
> py> a, b = 1234567, []  # immutable int, mutable list
> py> copy.copy(a) is a  # is the copy the same object as the original?
> True
> py> copy.copy(b) is b  # is the copy the same object as the original?
> False
> (To be precise, it is not the copy() function that refuses to make a
> copy. It the object itself: each object knows how to copy itself, and
> immutable ones will typically return themselves because they know it
> makes no difference.)

It appears to me that a logical consequence of a particular object
being immutable is that it also must be unique.

> For integers, floats, strings and other immutable objects, this is
> exactly what you want. There is no operation we can do to an immutable
> operation to change its value, so there is no way to distinguish between
> the same object twice or an object and a fresh copy.
> (Except for using the "is" operator, or the id() function.)

I should have thought to use "is".  I had seen "id()", but had not
previously investigated its usage.  Danny's answer inspired me to read
up on id() tonight.  Useful investigative tools!

There is another subtlety that I am not certain I am truly understanding:

py3: empty = []
py3: a = copy.copy(empty)
py3: a is empty
py3: a = [empty]
py3: b = copy.copy(a)
py3: a is b
py3: a[0] is b[0]
py3: c = copy.deepcopy(a)
py3: a is c
py3: a[0] is c[0]

Up to this point I am fine, but ...

py3: e = a*5
py3: e
[[], [], [], [], []]
py3: all(x is empty for x in e)

OK, I was expecting this.

py3: f = copy.deepcopy(e)
py3: any(x is empty for x in f)

Still OK ...

py3: all(x is f[0] for x in f)

But this I found mildly surprising.  I guess I was thinking that

f = copy.deepcopy(e) would be equivalent to manually entering "[]"
five times inside an enclosing list:

py3: f = [[], [], [], [], []]
py3: f[0] is f[1]
py3: all(x is f[0] for x in f)

So it surprised me that the deep copy created the same reference for
all members, just like in the original list.


More information about the Tutor mailing list