# [Tutor] Nested use of replication operator on lists

Steven D'Aprano steve at pearwood.info
Fri May 25 02:27:33 EDT 2018

```On Thu, May 24, 2018 at 10:33:56PM -0500, boB Stepp wrote:
[...]
> I am having trouble correlating the behavior of the one-dimensional
> case with the two-dimensional case.  The result of [1, 2]*3 seems to
> be an actual list, not a replication of the references to the items in
> the original list, [1, 2].

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. We can write our own version:

def replicate(alist, count):
newlist = []
for i in range(count):
newlist.extend(alist)
return newlist

Notice that we don't copy the items in alist. We just shove them into
the new list, repeatedly.

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.)

Let us go back to * the replicator operator. We can use "is" to check
for object identity:

py> obj = 987654321
py> alist = [obj]
py> assert alist[0] is obj
py> blist = alist*5
py> all(x is obj for x in blist)
True

So our blist contains five references to the same int object.

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.)

So when we have a list full of ints (or floats, strings, etc) the only
way we can change the value of the list is to *replace* the individual
objects with a brand new object:

py> blist
[987654321, 987654321, 987654321, 987654321, 987654321]
py> blist[0] = -1
py> blist[3] = -1
py> blist
[-1, 987654321, 987654321, -1, 987654321]

Since we're *replacing* the objects with a new object, the remaining
987654321 items don't change (indeed they can't change).

Now let us do the same with a list instead of an int:

py> obj = []
py> alist = [obj]
py> assert alist[0] is obj
py> blist = alist*5
py> all(x is obj for x in blist)
True

So far, the behaviour is identical. And replacing items works just like
it does with ints:

py> blist
[[], [], [], [], []]
py> blist[0] = [1, 2, 3]
py> blist
[[1, 2, 3], [], [], [], []]

But because mutable objects like lists can be modified in place, not
just replaced, we can do this:

py> blist[4].append(999)
py> blist
[[1, 2, 3], [999], [999], [999], [999]]

Touching the last item modifies all the other references to that same
list object, since the * operator doesn't make copies.

Think of it this way: I got into a fight the other day, on one side
there was Tom, Dick and Harry, but fortunately it was a fair fight
because on the other side there was me, myself and I.

> Also the "replication operator" does not seem to be replicating
> anything list-wise if it is instead replicating references to the
> original list's members.

It replicates the *contents* of the list into a new list, not the list
itself.

--
Steve
```