Thoughts on new vs traditional idioms

Raymond Hettinger python at rcn.com
Sun Feb 29 05:18:47 EST 2004


Copying
-------
To copy lists and dictionaries, the traditional idioms were l=mylist[:]
and d=mydict.copy().

When some of the builtin functions became types, a more consistent
approach became possible, l=list(mylist) and d=dict(mydict).

In general, mutable types can be copied using their type constructor.
This will also work with the new types being introduced in Py2.4,
s=set(myset) and d=collections.deque(mydeque).

For immutable types, the type constructors are smart enough to reuse the
existing value, so t=tuple(mytuple), s=str(mystring), and i=int(myint)
all return the original object (same identity) which can be used as if
it were a copy.

Taken together, the type constructors for mutable and immutable
types encapsulate much of the knowledge in the copy module (pertaining
to shallow copies).


Init
----
In Py2.2 and after, making new mutable types is separated into two steps,
__new__() for object creation and __init__() for initialization.

One implication of this change is being able to call __init__() directly
on an existing object to take advantage of some of the useful behaviors
builtin into __init__().

For lists, __init__(iterable) clears existing values and replaces them
with the elements from the iterable.  Where you used to write:
    
    a = [10, 20, 30, 40]
    a[:] = range(3)

You can now write:

    a = [10, 20, 30, 40]
    a.__init__(range(3))

I find the original approach to be clearer, but the second approach
generalizes to other types.  When used with dictionaries, __init__()
offers update behavior instead of replace behavior.  That is much
more flexible than the dict.update() method:

    d = {'a':1}
    d.__init__([('b',2), ('c',3)])   # update with an items list
    d.__init__(d=4, e=5)             # update with keyword arguments
    d.__init__(mydict)               # update with another dictionary

The item list update option is especially useful in conjunction with
enumerate() or itertools.izip() because the updates are run directly
from the iterable without consuming memory with a temporary list:

    dict(enumerate('abcdefg'))       # map letter positions to letters
    dict(izip('abcdefg', count()))   # map letters to letter positions
    d.__init__(izip(keys, values))   # update with corresponding keys and values

In Py2.4, the new mutable types, set() and collections.deque(), both
offer __init__() methods with update behavior.  So, the technique is
perfectly general and worth remembering.


Idioms and Obscurity
--------------------
Using a=constructor(b) for copying and a.__init__(arg) for updates
may seem obscure until they become standard idioms.  In time, I think
they seem more general and less cryptic than the older copy and
replace idioms, a=b[:] and a[:]=b.



More information about the Python-list mailing list