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 _______________________________________________ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor