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