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