On 04/19/2015 12:07 AM, boB Stepp wrote:

.....
Before Peter changed one of these
changeable objects, he had:

a = [1, ["x", "y"], 3]
b = a[:]

Now BOTH a[1] and b[1] now identify the location of the inner list
object, ["x", "y"] . Apparently, Python, in its ever efficient memory
management  fashion, when it creates the new object/piece of data
a[:], it sees no need to duplicate the inner list object, ["x", "y"],
but instead creates another identifier/pointer/reference to this
object's location. But since this inner list object is mutable, when
you change "y" to "hello!" in b, you also change it in a because both
a[1][1] and b[1][1] reference/point to the exact same storage location
where this element of the inner list is actually stored.

I hope this is helpful, and, if there are any misstepps, that when
they are revealed both of our understandings will be enhanced!


Some of your knowledge of other languages is leaking into your explanation. When we talk of the language Python, we need to distinguish between how CPython happens to be implemented, how other Python implementations happen to be created, and how C++ (for example) implements similar things. Some of the above use pointers, some do not. The language Python does not.

So especially when talking of inner lists, we need to clarify a few things.

An object has an identity, not a location. That identity can be checked with the 'is' operator, or the id() function. But it exists all the time.

Variables, as you say, do not contain an object, they reference it. And the formal term for that is binding. A name is bound to an object, to one object, at a time.

Now some objects have attributes, which is to say names, and those attributes are bound to other objects. So if we define a class, and have an instance of that class, and the instance has attributes, we can do something like:
    obj.inst_name

and get the particular attribute.

Still other objects have unnamed bindings. The canonical example is a list. A list object has a bunch of bindings to other objects. Even though each binding doesn't have a specific name, it nevertheless exists. And in this case we use integers to specify which of those bindings we want to follow. And we use a special syntax (the square bracket) to indicate which of these we want.

So let's take the simplest example:

mylist = ["a", "b"]

the name mylist is bound to a list object, which has two numbered bindings, called 0 and 1. That object's [0] binding is to a separate object "a". And the [1] binding is to a separate object "b"

There is another shorthand called a slice, which looks like [start, len, step] which lets us construct a *new* list from our list. And using defaults for all three terms lets us copy the list. But let's look at what the copy means:

newlist = mylist[:]
        -->  mylist[0, 2, 1]

This constructs a new list from the present one, where the zeroth location is bound to whatever object the first list's zeroth location was bound to. And so on for the oneth location, the twoth, etc.

Only one new list object is built, and no new objects are made for it. It just gets new bindings to the same things the first list had.

At this point, it should be clear what a shallow copy means. If the original list's oneth item was a binding to another list object, *that* list object does NOT get copied.

I don't like the term "inner list", but I don't know if it's incorrect. It's just misleading, since to the slice operation, the fact that it's a list is irrelevant. It's just an object whose binding is to be copied.

--
DaveA
_______________________________________________
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor

Reply via email to