[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


More information about the Tutor mailing list