[Python-Dev] from tuples to immutable dicts

Armin Rigo arigo@tunes.org
Sat, 23 Nov 2002 02:32:46 -0800 (PST)


Hello everybody,

There are some (admittedly occasional) situations in which an immutable dictionary-like type would be
handy, e.g. in the C core to implement switches or to speed up keyword argument matching.

Here is a related suggestion: enhancing or subclassing tuples to allow items to be named. Example
syntax:

    (1,2,c=3,d=4)

would build the 4-tuple (1,2,3,4) with names on the last two items.

I believe that it fills a hole between very small structures (e.g. (x,y) coordinates for points) and
large structures naturally implemented as classes: it allows small structures to grow a bit without
turning into an obscure 5- or 10-tuple. As a typical example, consider the os.stat() trick: it
currently returns an object that behaves like a 10-tuple, but whose items can also be read via
attributes for the sake of clarity. It only seems natural to be allowed to do:

    p = (x=2, y=3)
    print p.x          # 2
    print p[-1]        # 3
    print list(p)      # [2,3]

Of course, the syntax to build such tuples is chosen to match the call syntax. Conversely, tuple
unpacking could use exactly the same syntax as function argument lists:

    x = (1, 2)
    (a, b, c=3) = x             # set a=1, b=2, c=3
    a, b, c = (b=2, c=3, a=1)   # idem

The notion might unify the two special * and ** arguments, which are set respectively to an
_immutable_ list and a _mutable_ new dictionary. A single special argument (***?) might be used to get
the extra arguments as a tuple with possibly named items. As a side effect the function can know in
which order the keyword arguments were passed in, which may or may not be a good idea a priori but
which I sometimes wish I could do.

    def f(x, ***rest):
        print rest

    f(1,2,3)        # -> (2, 3)
    f(1,2,c=3,d=4)  # -> (2, c=3, d=4)
    f(1,2,x=3,y=4)  # -> TypeError ('x' gets two values)
    f(w=1,x=2,y=3)  # -> (w=1, y=3)

Questions:

* use 'tuple' or a new subtype 'namedtuple' or 'structure'?

* the suggested syntax emphasis the use of strings for the keys, but the constructor could be more
general, taking arbitrary hashable values:

    t = namedtuple([(1,2), (3,4)])
    t = namedtuple({1:2, 3:4})
    dict(t)  does not work: confusion with dict() of a sequence of pairs
    dict(**t) -> {1:2, 3:4}    # based on Just's idea of extra keyword arguments to dict()

* how do we read the key names? It seems impossible to add methods to namedtuples since all
attributes should potentially be reserved for item reads. Some bad ideas:

    p = (1, 2, x=3, y=4)
        p.__keys__()  ->  [None, None, 'x', 'y']    # special method name
        p.__key__(2)  ->  'x'
    or  p.__keys__    ->  (None, None, 'x', 'y')    # special attribute
    or  p % 2         ->  'x'                       # new operator (abusing % again)
    or  iterkeys(p)   ->  iterable                  # global function (urgh)

Note that dict(p)  ->  {'x': 3, 'y': 4}  is a partial solution, but it makes inspection heavier and
looses all info about key order and unnamed items. Besides,  dict((1,2,3)) -> {}  looks like a bad
thing to do.

* shoud name collisions be allowed inside a namedtuple?

* what about * and ** call syntaxes? For compatibility we might have to do

    p = (1, 2, x=3, y=4)
    f(*p)       # treat p as a tuple, always ignoring names (?)
    f(**p)      # only uses the named items of p (??)
    f(***p)     # same as f(1, 2, x=3, y=4)

or maybe push forward a shorter form of *** and deprecate * and **...?

* dissymetries between namedtuples and dicts: operations like 'in' and iteration operates on values in
tuples, but on keys in dicts...


Armin.