[Tutor] Tuples, Dictionarys and mutable vs. immutable

Tim Peters tim.one@home.com
Mon, 26 Feb 2001 03:18:28 -0500


[Sheila King]
> ...
> OK, an immutable type, such as a tuple, cannot be changed. Once
> created, it is assigned a memory location,

That last part is true, but is not unique to tuples, of course.  *Every*
object needs some memory to live in, even None.

> and if I do something like this:
>
> >>> z=(1,2)
> >>> z+=(3,4)
> >>> print z
> (1, 2, 3, 4)
> >>>
>
> The tuple I end up with is not the same as the one I started
> with. It has a different memory location, and a different id().

Right.  By the way, under the covers, id(x) *is* the memory location of x.

> However, based on what Remco wrote earlier in this thread, I'd guess
> that the memory for the tuple (1,2) has now been freed, if it has
> no name assigned to it,

It may or may not be, but it *can* be freed.  The language doesn't define
*when* unreachable memory will be reused.  CPython generally reuses memory
very quickly; Jython generally does not.

> and I can now assume that the operating system now has access to
> that memory location again?

Python can reuse it again, which is what I believe you really want to know.
Whether the operating system has access to it is a different question, and
the answer varies across platforms (it involves platform-specific details
about how the C library functions malloc() and free() are implemented on the
platform, and about operating system policies, and ... trust me, you don't
really want to know the answer to that one <wink>).

> OK, but now the mutable types:  lists and dictionaries.
>
> I know a dictionary has a copy method, so that I can have a new
> dictionary with same contents, without having to worry about modifying
> the contents of the original:
>
> >>> dict={1:"one", 2:"two"}
> >>> dict
> {2: 'two', 1: 'one'}
> >>> id(dict)
> 12029900
> >>> dict2 = dict.copy()
> >>> id(dict2)
> 12064908
> >>>

Note that this a "shallow copy", though:

>>> d = {1: [1, 2]}
>>> e = d.copy()
>>> d[1].append(3)
>>> e
{1: [1, 2, 3]}
>>>

Mututating the list [1, 2] from d also affects the list in the shallow copy
e, because a shallow copy only copies references to the objects; it does not
descend *into* objects to make copies of all their components too.

Shallow copies are *usually* what you want.  The copy *module* supplies a
deepcopy() function if you want a "deep copy":

>>> import copy
>>> d = {1: [1, 2]}
>>> e = copy.deepcopy(d)
>>> d[1].append(3)
>>> e
{1: [1, 2]}
>>> d
{1: [1, 2, 3]}
>>>

There the deep copy e isn't affected by anything you do to d *or* to
anything you to do anything *inside* d.

> What about lists? So far as I can determine, they do not have a
> copy method, and I would be modifying the "original" list, if I
> tried to assign it to a second variable name and then make
> modifications to that one, without modifying the original.

Yes, and the first dict example above actually illustrates that.  There are
several ways to copy a list x:

y = x[:]  # makes a shallow copy; this is what you'll see most often
y = copy.deepcopy(x) # makes a deep copy
y = copy.copy(x) # makes a shallow copy
y = list(x) # makes a shallow copy
y = [z for z in x] # a shallow copy; requires 2.0 or later

x[:] is such a common and obvious (once you're used to it <wink>) way to
make a shallow copy of a list that there wasn't a real need to implement a
list.copy() method.  Slice notation (x[:]) doesn't apply to dicts, though,
and while copy.copy(dict) also works to make a shallow copy, shallow copies
of dicts are frequent enough that it seemed worthwhile to give dicts a
.copy() method.

> What is the proscribed method for handling lists so that one is a
> copy that can be modified without changing the original version?

x[:] or copy.deepcopy(x), depending on whether you want a shallow or deep
copy.  deep copies are more expensive to make.  Which one you need depends
entirely on *why* you're making a copy.  Shallow copies are usually good
enough.