[Python-ideas] namedtuple literals [Was: RE a new namedtuple]
Steven D'Aprano
steve at pearwood.info
Wed Jul 26 13:10:16 EDT 2017
On Thu, Jul 27, 2017 at 02:05:47AM +1000, Nick Coghlan wrote:
> On 26 July 2017 at 11:05, Steven D'Aprano <steve at pearwood.info> wrote:
> > I don't see any way that this proposal can be anything by a subtle
> > source of bugs. We have two *incompatible* requirements:
> >
> > - we want to define the order of the fields according to the
> > order we give keyword arguments;
> >
> > - we want to give keyword arguments in any order without
> > caring about the field order.
> >
> > We can't have both, and we can't give up either without being a
> > surprising source of annoyance and bugs.
>
> I think the second stated requirement isn't a genuine requirement, as
> that *isn't* a general expectation.
>
> After all, one of the reasons we got ordered-by-default keyword
> arguments is because people were confused by the fact that you
> couldn't reliably do:
>
> mydict = collections.OrderedDict(x=1, y=2)
Indeed. But the reason we got *keyword arguments* in the first place was
so you didn't need to care about the order of parameters. As is often
the case, toy examples with arguments x and y don't really demonstrate
the problem in real code. We need more realistic, non-trivial examples.
Most folks can remember the first two arguments to open:
open(name, 'w')
but for anything more complex, we not only want to skip arguments and
rely on their defaults, but we don't necessarily remember the order of
definition:
open(name, 'w', newline='\r', encoding='macroman', errors='replace')
Without checking the documentation, how many people could tell you
whether that order matches the positional order? I know I couldn't.
You say
> ntuple(x=1, y=2) == ntuple(y=1, x=2) == tuple(1, 2)
> ntuple(x=2, y=1) == ntuple(y=2, x=1) == tuple(2, 1)
>
> Putting the y-coordinate first would be *weird* though
Certainly, if you're used to the usual mathematics convention that the
horizontal coordinate x comes first. But if you are used to the curses
convention that the vertical coordinate y comes first, putting y first
is completely natural.
And how about ... ?
ntuple(flavour='strange', spin='1/2', mass=95.0, charge='-1/3',
isospin='-1/2', hypercharge='1/3')
versus:
ntuple(flavour='strange', mass=95.0, spin='1/2', charge='-1/3',
hypercharge='1/3', isospin='-1/2')
Which one is "weird"?
This discussion has been taking place for many days, and it is only now
(thanks to MRAB) that we've noticed this problem. I think it is
dangerous to assume that the average Python coder will either:
- always consistently specify the fields in the same order;
- or recognise ahead of time (during the design phase of the program)
that they should pre-declare a class with the fields in a particular
order.
Some people will, of course. But many won't. Instead, they'll happily
start instantiating ntuples with keyword arguments in inconsistent
order, and if they are lucky they'll get unexpected, tricky to debug
exceptions. If they're unlucky, their program will silently do the wrong
thing, and nobody will notice that their results are garbage.
SimpleNamespace doesn't have this problem: the fields in SimpleNamespace
aren't ordered, and cannot be packed or unpacked by position.
namedtuple doesn't have this problem: you have to predeclare the fields
in a certain order, after which you can instantiate them by keyword in
any order, and unpacking the tuple will always honour that order.
> Now, though, that's fully supported and does exactly what you'd expect:
>
> >>> from collections import OrderedDict
> >>> OrderedDict(x=1, y=2)
> OrderedDict([('x', 1), ('y', 2)])
> >>> OrderedDict(y=2, x=1)
> OrderedDict([('y', 2), ('x', 1)])
>
> In this case, the "order matters" expectation is informed by the
> nature of the constructor being called: it's an *ordered* dict, so the
> constructor argument order matters.
I don't think that's a great analogy. There's no real equivalent of
packing/unpacking OrderedDicts by position to trip us up here. It is
better to think of OrderedDicts as "order-preserving dicts" rather than
"dicts where the order matters". Yes, it does matter, in a weak sense.
But not in the important sense of binding values to keys:
py> from collections import OrderedDict
py> a = OrderedDict([('spam', 1), ('eggs', 2)])
py> b = OrderedDict([('eggs', -1), ('spam', 99)])
py> a.update(b)
py> a
OrderedDict([('spam', 99), ('eggs', -1)])
update() has correctly bound 99 to key 'spam', even though the keys are
in the wrong order. The same applies to dict unpacking:
a.update(**b)
In contrast, named tuples aren't just order-preserving. The field order
is part of their definition, and tuple unpacking honours the field
order, not the field names. While we can't update tuples in place, we
can and often do unpack them into variables. When we do, we need to know
the field order:
flavour, charge, mass, spin, isospin, hypercharge = mytuple
but the risk is that the field order may not be what we expect unless we
are scrupulously careful to *always* call ntuple(...) with the arguments
in the same order.
--
Steve
More information about the Python-ideas
mailing list