Python’s nicely evolved its standard data structures. Bringing set into the core, adding OrderedDict (and friends), and establishing collections ABCs—all these up-level common facilities, broadly improving program clarity and correctness. I’d like Python to take the next step, especially regarding ordering. Using a compatible, separate implementation for OrderedDict is a fine way to gracefully extend the language, but it leaves ordering only half-accomodated. Consider: OrderedDict(a=2, b=3, c=7) yields: OrderedDict([('a', 2), ('c', 7), ('b', 3)]) The items are immediately disordered, having been jumbled passing through a conventional dict. One can initialize using a list of items, of course, but that reminds me of the line from NetHack: “You enter what seems to be an older, more primitive world.” Everyone rightly accepts doing a bit more specification for truly “extra” data structure features—persistence, say. And if falling back to lists of tuples is the best that can be done for ordered structures, well, okay. We’ll live with it. But from an app developer’s point of view, ordering is a basic, essential property. It seems like something that should be gracefully accommodated, as a built-in, rather than as “an extra” or something that requires falling back to Late Medieval Python. kwargs arrived in 1.4, back in 1996, right? So I propose that kwargs, at least, default to an ordered mapping rather than a pure hash mapping. Ideally, {...} literals would also be ordered. I suspect this will be an unpopular idea among implementers, for whom unordered dict is a pervasive and long-optimized tool. But this is a correctness, or at least a consistency, issue. I don’t see any elegant alternative way to initialize ordered data structures unless the modern Python initialization idiom(s), esp. kwargs, themselves observe order. Historically, sort features were usually unstable because that’s easier to implement and faster to run. Over time, stable sort has become the norm, either as an option (e.g. GNU’s sort --stable, Perl’s use sort 'stable' as of 5.8) or implicitly (e.g. Python’s sorted, as of 2.2). Over time, getting better results proved more broadly important than getting near-correct results faster; and both by code optimization and system improvement, the associated time or space cost of stable ordering was mooted. I’d like the same to happen for Python mappings. It’s my understanding that Ruby 1.9 has recently made this shift.
On May 14, 2013, at 12:53, Jonathan Eunice
Using a compatible, separate implementation for OrderedDict is a fine way to gracefully extend the language, but it leaves ordering only half-accomodated. Consider:
OrderedDict(a=2, b=3, c=7) If your proposal is to replace dict with OrderedDict, I think you need at least one use case besides OrderedDict's constructor. But from an app developer’s point of view, ordering is a basic, essential property.
There are plenty of things that are basic, essential properties of a particular type, but there is very little that's a basic, essential property of _all_ types. Surely you wouldn't suggest that a complex number should remember whether you specified the real or imaginary component first. So your argument is that order of insertion is a basic property of _mappings_ in particular. And I think blist.sorteddict, trie.Trie, etc. are good arguments against even that assertion. It's not just about performance; it's about correctness. Insertion order is not a fundamental property of mappings. If you're just suggesting that collections.abc should grow an OrderedMapping, and/or that kwargs should be an OrderedDict, either or both might be reasonable. But if you're suggesting that Mapping and dict should both become ordered, I disagree with both.
On Tue, May 14, 2013 at 5:23 PM, Andrew Barnert
On May 14, 2013, at 12:53, Jonathan Eunice
wrote: Using a compatible, separate implementation for OrderedDict is a fine way to gracefully extend the language, but it leaves ordering only half-accomodated. Consider:
OrderedDict(a=2, b=3, c=7)
If your proposal is to replace dict with OrderedDict, I think you need at least one use case besides OrderedDict's constructor.
I don't understand the dismissal of OrderedDict.__init__ as an invalid use case. It would be a substantial usability improvement to special-case OrderedDict at compile-time purely to get the ability to instantiate odict literals (not that I'm suggesting that). In the interest of moving the discussion forward, I've had a few use cases along these lines. Let's say I want to create simple HTML elements by hand: def create_element(tag, text='', **attributes): attrs = ['{}="{}"'.format(k,v) for k, v in attributes.items()] return "<{0} {1}>{2}{0}>".format(tag, ' '.join(attrs), text) print(create_element('img', alt="Some cool stuff.", src="coolstuff.jpg")) <img src="coolstuff.jpg" alt="Some cool stuff."></img> In python today, if I want to the attributes to retain the order in which they appear as arguments, the function has to be changed in such a way that all calling code is forced to look like some variation on this theme: >>> create_element('img', [('alt', 'Some cool stuff.'), ('src', 'coolstuff.jpg')]) It's not that it's impossible to do, it's that dict-based API's would benefit from the function being able to decide on its own whether or not it cared about the order of arguments. Having to express a kwargs-based or plain-old-dict-based function as a list-of-2-tuples function is... uncool. ;-)
On 05/14/2013 06:57 PM, Don Spaulding wrote:
On Tue, May 14, 2013 at 5:23 PM, Andrew Barnert
mailto:abarnert@yahoo.com> wrote: On May 14, 2013, at 12:53, Jonathan Eunice
mailto:jonathan.eunice@gmail.com> wrote: Using a compatible, separate implementation for |OrderedDict| is a fine way to gracefully extend the language, but it leaves ordering only half-accomodated. Consider:
OrderedDict(a=2, b=3, c=7)
If your proposal is to replace dict with OrderedDict, I think you need at least one use case besides OrderedDict's constructor.
I don't understand the dismissal of OrderedDict.__init__ as an invalid use case.
It's not being dismissed, but it's only one. There are thousands of functions using **kwds that simply don't care about the order. Should they all pay the performance price so that some tiny fraction can benefit? While it is correctly said that if performance is a Big Deal you shouldn't be using Python, we also are not interested in making it slower without a really good reason. -- ~Ethan~
On Tue, May 14, 2013 at 10:07 PM, Ethan Furman
On 05/14/2013 06:57 PM, Don Spaulding wrote:
On Tue, May 14, 2013 at 5:23 PM, Andrew Barnert
mailto:abarnert@yahoo.com> wrote: On May 14, 2013, at 12:53, Jonathan Eunice
mailto:jonathan.eunice@gmail.com> wrote: Using a compatible, separate implementation for |OrderedDict| is a fine way to gracefully extend the language, but it leaves ordering only half-accomodated. Consider:
OrderedDict(a=2, b=3, c=7)
If your proposal is to replace dict with OrderedDict, I think you need at least one use case besides OrderedDict's constructor.
I don't understand the dismissal of OrderedDict.__init__ as an invalid use case.
It's not being dismissed, but it's only one. There are thousands of functions using **kwds that simply don't care about the order. Should they all pay the performance price so that some tiny fraction can benefit?
While it is correctly said that if performance is a Big Deal you shouldn't be using Python, we also are not interested in making it slower without a really good reason.
-- ~Ethan~
_______________________________________________ Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas
I'm not convinced that the performance argument is valid. There are clever ways to optimize ordered dicts. It would be quite a change to the language.
On 05/14/2013 07:38 PM, Daniel Holth wrote:
On Tue, May 14, 2013 at 10:07 PM, Ethan Furman
wrote: On 05/14/2013 06:57 PM, Don Spaulding wrote:
On Tue, May 14, 2013 at 5:23 PM, Andrew Barnert
mailto:abarnert@yahoo.com> wrote: On May 14, 2013, at 12:53, Jonathan Eunice
mailto:jonathan.eunice@gmail.com> wrote: Using a compatible, separate implementation for |OrderedDict| is a fine way to gracefully extend the language, but it leaves ordering only half-accomodated. Consider:
OrderedDict(a=2, b=3, c=7)
If your proposal is to replace dict with OrderedDict, I think you need at least one use case besides OrderedDict's constructor.
I don't understand the dismissal of OrderedDict.__init__ as an invalid use case.
It's not being dismissed, but it's only one. There are thousands of functions using **kwds that simply don't care about the order. Should they all pay the performance price so that some tiny fraction can benefit?
While it is correctly said that if performance is a Big Deal you shouldn't be using Python, we also are not interested in making it slower without a really good reason.
I'm not convinced that the performance argument is valid. There are clever ways to optimize ordered dicts. It would be quite a change to the language.
Best way to find out is branch and try it. Then we'll have hard numbers instead of lots of hand waving and opinions. ;) If any performance hit is negligible I would certainly be interested in having them. -- ~Ethan~
On Tue, May 14, 2013 at 9:07 PM, Ethan Furman
On 05/14/2013 06:57 PM, Don Spaulding wrote:
On Tue, May 14, 2013 at 5:23 PM, Andrew Barnert
> wrote: On May 14, 2013, at 12:53, Jonathan Eunice
>> wrote: Using a compatible, separate implementation for |OrderedDict| is a
fine way to gracefully extend the language, but it leaves ordering only half-accomodated. Consider:
OrderedDict(a=2, b=3, c=7)
If your proposal is to replace dict with OrderedDict, I think you need at least one use case besides OrderedDict's constructor.
I don't understand the dismissal of OrderedDict.__init__ as an invalid use case.
It's not being dismissed, but it's only one. There are thousands of functions using **kwds that simply don't care about the order. Should they all pay the performance price so that some tiny fraction can benefit?
While it is correctly said that if performance is a Big Deal you shouldn't be using Python, we also are not interested in making it slower without a really good reason.
I'm of the opinion that the status quo is "fast, but kinda wrong". Ideally we'd have a "fast, and correct" implementation, but I'd settle for a "negligibly slower, but still correct" implementation. Nobody wants Python 3-dot-next to be slower, but how much slower are we really talking about given that Raymond Hettinger recently proposed[0] a plain-old-dict implementation that uses less space, performs better, and as an unintended side-effect just happens to maintain its initial order? Aside from the performance impact, isn't any code that relies on any current ordering behavior of **kwargs broken by design? [0]: http://mail.python.org/pipermail/python-dev/2012-December/123028.html
On 15 May 2013 12:56, Don Spaulding
Raymond Hettinger recently proposed[0] a plain-old-dict implementation that uses less space, performs better, and as an unintended side-effect just happens to maintain its initial order?
[0]: http://mail.python.org/pipermail/python-dev/2012-December/123028.html
Whoops - that's what I was referring to, but misattributed it to Barry. Tim Delaney
On Tue, May 14, 2013, at 21:57, Don Spaulding wrote:
I don't understand the dismissal of OrderedDict.__init__ as an invalid use case. It would be a substantial usability improvement to special-case OrderedDict at compile-time purely to get the ability to instantiate odict literals (not that I'm suggesting that).
Maybe we should be talking about literals. OrderedDict(a=3, b=3, c=7) is not and never will be a literal.
On Wed, May 15, 2013 at 10:20 AM,
On Tue, May 14, 2013, at 21:57, Don Spaulding wrote:
I don't understand the dismissal of OrderedDict.__init__ as an invalid use case. It would be a substantial usability improvement to special-case OrderedDict at compile-time purely to get the ability to instantiate odict literals (not that I'm suggesting that).
Maybe we should be talking about literals. OrderedDict(a=3, b=3, c=7) is not and never will be a literal.
Forgive my misuse of the term 'literal' here. I meant only to say that anywhere you currently use a plain-old-dictionary literal, there's no way to easily switch it to a value which preserves its order. For example: foo = { 'b': 1, 'a': 2 } Has to turn into something far less appealing: foo = OrderedDict() foo['b'] = 1 foo['a'] = 2 # or... foo = OrderedDict([ ('b', 1), ('a', 2) ]) Even if the only ordering change that was made was to magically give OrderedDict.__init__ its **kwargs in order, it would clean up these instances, which I initially referred to as literals. foo = OrderedDict( b=1, a=2 ) I was explicitly not advocating that change, just noting that the OrderedDict.__init__ use case is a perfect example of how this would be used to enable something that currently isn't possible without, IMO, extra noise in the definition of dicts of this nature.
2013/5/15 Don Spaulding
Even if the only ordering change that was made was to magically give OrderedDict.__init__ its **kwargs in order, it would clean up these instances, which I initially referred to as literals.
foo = OrderedDict( b=1, a=2 )
Since PEP3115, classes can __prepare__ a custom dict: from collections import OrderedDict class OrderedDictBuilder(type): @classmethod def __prepare__(metacls, name, bases): return OrderedDict() def __new__(cls, name, bases, classdict): del classdict['__module__'] # ugh return classdict Then we can (ab)use the Class syntax to preserve the order! class foo(metaclass=OrderedDictBuilder): b = 1 a = 2 assert repr(foo) == "OrderedDict([('b', 1), ('a', 2)])" There is probably a way to get rid of the "metaclass=" part. I'm not sure to like it, though. -- Amaury Forgeot d'Arc
On 05/15/2013 09:27 AM, Amaury Forgeot d'Arc wrote:
2013/5/15 Don Spaulding
mailto:donspauldingii@gmail.com> Even if the only ordering change that was made was to magically give OrderedDict.__init__ its **kwargs in order, it would clean up these instances, which I initially referred to as literals.
foo = OrderedDict( b=1, a=2 )
Since PEP3115, classes can __prepare__ a custom dict:
from collections import OrderedDict class OrderedDictBuilder(type): @classmethod def __prepare__(metacls, name, bases): return OrderedDict() def __new__(cls, name, bases, classdict): del classdict['__module__'] # ugh return classdict
Then we can (ab)use the Class syntax to preserve the order!
class foo(metaclass=OrderedDictBuilder): b = 1 a = 2
assert repr(foo) == "OrderedDict([('b', 1), ('a', 2)])"
There is probably a way to get rid of the "metaclass=" part. I'm not sure to like it, though.
class OrderedDict(metaclass=OrderedDictBuilder): pass class foo(OrderedDict): b = 1 a = 2 -- ~Ethan~
On 05/15/2013 09:40 AM, Ethan Furman wrote:
On 05/15/2013 09:27 AM, Amaury Forgeot d'Arc wrote:
There is probably a way to get rid of the "metaclass=" part. I'm not sure to like it, though.
class OrderedDict(metaclass=OrderedDictBuilder): pass
class foo(OrderedDict): b = 1 a = 2
Argh, please disregard -- I should have read more carefully. -- ~Ethan~
From: Don Spaulding
On Tue, May 14, 2013 at 5:23 PM, Andrew Barnert
wrote: On May 14, 2013, at 12:53, Jonathan Eunice
wrote: Using a compatible, separate implementation for OrderedDict is a fine way to gracefully extend the language, but it leaves ordering only half-accomodated. Consider:
OrderedDict(a=2, b=3, c=7) If your proposal is to replace dict with OrderedDict, I think you need at least one use case besides OrderedDict's constructor.
I don't understand the dismissal of OrderedDict.__init__ as an invalid use case. It would be a substantial usability improvement to special-case OrderedDict at compile-time purely to get the ability to instantiate odict literals (not that I'm suggesting that).
I'm not dismissing it. If it were one of multiple varied use cases, it would contribute to the argument. But if the _only_ use case is something this special (we can't pass an OrderedDict to it, because that's obviously circular), it's not a good argument for a change with wide-ranging effects.
In the interest of moving the discussion forward, I've had a few use cases along these lines. Let's say I want to create simple HTML elements by hand:
def create_element(tag, text='', **attributes): attrs = ['{}="{}"'.format(k,v) for k, v in attributes.items()] return "<{0} {1}>{2}{0}>".format(tag, ' '.join(attrs), text) print(create_element('img', alt="Some cool stuff.", src="coolstuff.jpg")) <img src="coolstuff.jpg" alt="Some cool stuff."></img>
Well, HTML explicitly assigns no meaning to the order of attributes. And I think this is a symptom of a larger problem. Every month, half a dozen people come to StackOverflow asking how to get an ordered dictionary. Most of them are asking because they want to preserve the order of JSON objects—which, again, is explicitly defined as unordered. If code relies on the order of HTML attributes, or JSON object members, it's wrong, and it's going to break, and it's better to find that out early. All that being said, sometimes HTML and JSON are read by humans as well as by software, at least for debugging purposes, and sometimes it's more readable with a specific (or at least consistent and predictable) order. So, the suggestion is definitely not without merit. For some tests, you'll want the order scrambled to make sure you're not incorrectly relying on order, but, on the other hand, for debugging the output, you'll want it ordered to make it more readable.
It's not that it's impossible to do, it's that dict-based API's would benefit from the function being able to decide on its own whether or not it cared about the order of arguments. Having to express a kwargs-based or plain-old-dict-based function as a list-of-2-tuples function is... uncool. ;-)
This is an interesting idea. If there were a way for the function to decide what type is used for creating its kwargs, you could do all kinds of cool things—have that switch you could turn on or off I just mentioned for different kinds of testing, or preserve order in "debug mode" but leave it arbitrary and as fast as possible in "production mode", or take a blist.sorteddict if you're intending to stash it and use it as the starting point for a blist.sorteddict anyway, or whatever. And it wouldn't affect the 99% of functions that don't care. The syntax seems pretty obvious: def kwargs(mapping_constructor): def deco(fn): fn.kwargs_mapping_constructor = mapping_constructor return fn return deco @kwargs(OrderedDict) def foo(a, b, *args, **kwargs): pass Handling this at the calling site is a bit harder, but still not that hard. And this even solves the special problem of OrderedDict seemingly needing an OrderedDict: Just give it a mapping_constructor that creates a list of tuples.
On Wed, May 15, 2013 at 1:01 PM, Andrew Barnert
From: Don Spaulding
Sent: Tuesday, May 14, 2013 6:57 PM In the interest of moving the discussion forward, I've had a few use cases along these lines. Let's say I want to create simple HTML elements by hand:
def create_element(tag, text='', **attributes): attrs = ['{}="{}"'.format(k,v) for k, v in attributes.items()] return "<{0} {1}>{2}{0}>".format(tag, ' '.join(attrs), text)
print(create_element('img', alt="Some cool stuff.",
src="coolstuff.jpg"))
<img src="coolstuff.jpg" alt="Some cool stuff."></img>
Well, HTML explicitly assigns no meaning to the order of attributes. And I think this is a symptom of a larger problem. Every month, half a dozen people come to StackOverflow asking how to get an ordered dictionary. Most of them are asking because they want to preserve the order of JSON objects—which, again, is explicitly defined as unordered. If code relies on the order of HTML attributes, or JSON object members, it's wrong, and it's going to break, and it's better to find that out early.
Yes, I'm aware that HTML and JSON are explicit about the fact that order should not matter to parsers. But just because I know that, and you know that, doesn't mean that the person in charge of developing the XML-based or JSON-based web service I'm trying to write a wrapper for knows that. Twice now I've encountered poorly-written web services that have choked on something like: <request> <action>modifyStuff</action> <user>user_123456</user> </request> ...with an error to the effect of "Cannot modifyStuff without specifying user credentials". So someone else has built a system around an XML parser that doesn't know that sibling elements aren't guaranteed to appear in any particular order. Obviously the best fix is for them to use a different parser, but my point is that there's no fix available to my function short of writing all calling locations into a list-of-tuples format. My function doesn't care about the order per se, it just doesn't want to *change the order* of the input while it's generating the output. As another example, the most recent instance where I've wanted to upgrade a regular dict to an odict, was when discovering a bug in a coworker's lookup table. It was a table that mapped an employee_type string to a Django queryset to be searched for a particular user_id. Consider this lookup function: EMPLOYEE_TYPES = { 'agent': Agent.objects.all(), 'staff': Staff.objects.all(), 'associate': Associate.objects.all() } def get_employee_type(user_id): for typ, queryset in EMP_TYPES.items(): if queryset.filter(user_id=user_id).exists(): return typ The bug we hit was because we cared about checking each queryset in the order they were specified. My coworker knows that dicts are unordered, but it slipped his mind while writing this code. Regardless, once you find a bug like this, what are your options for fixing this to process the lookups in a specific order? The first thing you think of is, "Oh, I just need to use an OrderedDict.". Well, technically yes, except there's no convenient way to instantiate an OrderedDict with more than one element at a time. So now you're back to rewriting calling sites into order-preserving lists-of-tuples again. Which is why I think the OrderedDict.__init__ case is in and of itself compelling. ;-)
It's not that it's impossible to do, it's that dict-based API's would benefit from the function being able to decide on its own whether or not it cared about the order of arguments. Having to express a kwargs-based or plain-old-dict-based function as a list-of-2-tuples function is... uncool. ;-)
This is an interesting idea. If there were a way for the function to decide what type is used for creating its kwargs, you could do all kinds of cool things—have that switch you could turn on or off I just mentioned for different kinds of testing, or preserve order in "debug mode" but leave it arbitrary and as fast as possible in "production mode", or take a blist.sorteddict if you're intending to stash it and use it as the starting point for a blist.sorteddict anyway, or whatever. And it wouldn't affect the 99% of functions that don't care.
The syntax seems pretty obvious:
def kwargs(mapping_constructor): def deco(fn): fn.kwargs_mapping_constructor = mapping_constructor return fn return deco
@kwargs(OrderedDict) def foo(a, b, *args, **kwargs): pass
That's an interesting concept. It would certainly address the most common need I see for better OrderedDict support in the language.
Handling this at the calling site is a bit harder, but still not that hard.
I don't see how this would require changes to the calling site. Can you elaborate?
Hang on, what? Now, maybe that's true of your _particular_ XML spec. And this certainly seems symptomatic of a larger problem (e.g. firing off an event as soon as the action tag closes, rather than parsing the whole document). But XML elements are _not_ unordered. On Wed, May 15, 2013, at 15:35, Don Spaulding wrote:
Twice now I've encountered poorly-written web services that have choked on something like:
<request> <action>modifyStuff</action> <user>user_123456</user> </request>
...with an error to the effect of "Cannot modifyStuff without specifying user credentials". So someone else has built a system around an XML parser that doesn't know that sibling elements aren't guaranteed to appear in any particular order.
-- Random832 (top-posted because my reply and your message aren't guaranteed to appear in any particular order.)
On Wed, May 15, 2013 at 3:01 PM,
Hang on, what?
Now, maybe that's true of your _particular_ XML spec. And this certainly seems symptomatic of a larger problem (e.g. firing off an event as soon as the action tag closes, rather than parsing the whole document). But XML elements are _not_ unordered.
Hmm, when I ran across this particular problem, I recall seeing somewhere that the preservation of element order in the spec is undefined. And indeed it appears that while attributes are called explicitly as not having significant order, the spec doesn't actually weigh in on element order one way or another. However, regardless of what's in the spec, it would seem that everyone just assumes element order to be significant anyway, so it doesn't really matter. http://lists.xml.org/archives/xml-dev/200101/msg00841.html
On Wed, May 15, 2013, at 15:35, Don Spaulding wrote:
Twice now I've encountered poorly-written web services that have choked on something like:
<request> <action>modifyStuff</action> <user>user_123456</user> </request>
...with an error to the effect of "Cannot modifyStuff without specifying user credentials". So someone else has built a system around an XML parser that doesn't know that sibling elements aren't guaranteed to appear in any particular order.
-- Random832 (top-posted because my reply and your message aren't guaranteed to appear in any particular order.)
OrderedDict(I='see', what='you', did='there') OrderedDict([('did', 'there'), ('I', 'see'), ('what', 'you')])
On Wed, May 15, 2013, at 16:36, Don Spaulding wrote:
Hmm, when I ran across this particular problem, I recall seeing somewhere that the preservation of element order in the spec is undefined. And indeed it appears that while attributes are called explicitly as not having significant order, the spec doesn't actually weigh in on element order one way or another. However, regardless of what's in the spec, it would seem that everyone just assumes element order to be significant anyway, so it doesn't really matter.
Just remember, HTML is an XML dialect. If assumption leads to the conclusion that paragraphs could be randomly shuffled around on a page, it's probably wrong. Now, _specific_ XML specs (i.e. the one that specifically defines "request" for the web service you were using) _can_ be defined in a way that doesn't care about order, and many do, but it's not something about XML itself.
random832 is right, XML elements are ordered. But this is a tangent. HTML attributes, JSON objects, and plenty of other types are _not_ ordered. So, even if your example is bad, you can trivially sub it into an equivalent example that's good.
________________________________ From: Don Spaulding
To: random832@fastmail.us Cc: python-ideas Sent: Wednesday, May 15, 2013 1:36 PM Subject: Re: [Python-ideas] Let's be more orderly! On Wed, May 15, 2013 at 3:01 PM,
wrote: Hang on, what?
Now, maybe that's true of your _particular_ XML spec. And this certainly seems symptomatic of a larger problem (e.g. firing off an event as soon as the action tag closes, rather than parsing the whole document). But XML elements are _not_ unordered.
Hmm, when I ran across this particular problem, I recall seeing somewhere that the preservation of element order in the spec is undefined. And indeed it appears that while attributes are called explicitly as not having significant order, the spec doesn't actually weigh in on element order one way or another. However, regardless of what's in the spec, it would seem that everyone just assumes element order to be significant anyway, so it doesn't really matter.
http://lists.xml.org/archives/xml-dev/200101/msg00841.html
On Wed, May 15, 2013, at 15:35, Don Spaulding wrote:
Twice now I've encountered poorly-written web services that have choked on something like:
<request> <action>modifyStuff</action> <user>user_123456</user> </request>
...with an error to the effect of "Cannot modifyStuff without specifying user credentials". So someone else has built a system around an XML parser that doesn't know that sibling elements aren't guaranteed to appear in any particular order.
-- Random832 (top-posted because my reply and your message aren't guaranteed to appear in any particular order.)
OrderedDict(I='see', what='you', did='there') OrderedDict([('did', 'there'), ('I', 'see'), ('what', 'you')])
Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas
From: Don Spaulding
On Wed, May 15, 2013 at 1:01 PM, Andrew Barnert
wrote: From: Don Spaulding
Sent: Tuesday, May 14, 2013 6:57 PM
In the interest of moving the discussion forward, I've had a few use cases along these lines. Let's say I want to create simple HTML elements by hand:
def create_element(tag, text='', **attributes): attrs = ['{}="{}"'.format(k,v) for k, v in attributes.items()] return "<{0} {1}>{2}{0}>".format(tag, ' '.join(attrs), text) print(create_element('img', alt="Some cool stuff.", src="coolstuff.jpg")) <img src="coolstuff.jpg" alt="Some cool stuff."></img>
Well, HTML explicitly assigns no meaning to the order of attributes. And I think this is a symptom of a larger problem. Every month, half a dozen people come to StackOverflow asking how to get an ordered dictionary. Most of them are asking because they want to preserve the order of JSON objects—which, again, is explicitly defined as unordered. If code relies on the order of HTML attributes, or JSON object members, it's wrong, and it's going to break, and it's better to find that out early.
Yes, I'm aware that HTML and JSON are explicit about the fact that order should not matter to parsers. But just because I know that, and you know that, doesn't mean that the person in charge of developing the XML-based or JSON-based web service I'm trying to write a wrapper for knows that. Twice now I've encountered poorly-written web services that have choked on something like:
I suppose when the other side is poorly-written and out of your control, that's also a legitimate use for ordering, along with human readability for debugging. (Or maybe it's the same case—the human brain cares about order even when you tell it not to, and that's out of your control…) But I think it's another case where maybe it _shouldn't_ be on by default. Explicitly asking for an OrderedDict is a great way of signaling that someone cares about order, whether or not they should, right?
The first thing you think of is, "Oh, I just need to use an OrderedDict.". Well, technically yes, except there's no convenient way to instantiate an OrderedDict with more than one element at a time. So now you're back to rewriting calling sites into order-preserving lists-of-tuples again. Which is why I think the OrderedDict.__init__ case is in and of itself compelling. ;-)
But if the OrderedDict.__init__ case were the only good case, coming up with some other way to create OrderedDict objects might be a better solution than changing kwargs. And if the OrderedDict solution automatically solved all of the other cases, that would _also_ mean that solving OrderedDict is what matters, not solving kwargs. You've already given cases that you could solve with Python as it is today, if only you had a good OrderedDict constructor. And, even for the cases that you _can't_ solve today, most of the obvious potential solutions will only work if OrderedDict is a solved problem, because they rely on OrderedDict. odict literals are an obvious example of that. So is my mapping_constructor idea. If everyone uses @kwargs(OrderedDict), then OrderedDict has to use @kwargs(_HackyOrderedDictBuilder), which is presumably some class that abuses the mapping protocol by wrapping custom __getitem__ and __setitem__ calls around list or something. Or consider this small change to the rules for passing **kwargs. Currently, Python guarantees to build a new dict-like object out of anything you pass, then update it. What if Python instead guaranteed to build a new mapping of the same type (e.g., via copy.copy), then update it in order? Then you could just do this: create_element('img', alt="Some cool stuff.", src="coolstuff.jpg", **OrderedDict()) Or take that last change, and also change the syntax to allow specifying default values for *args and **kwargs. Then: def create_element(tag, text='', **attributes=OrderedDict()): And so on. There are tons of possible designs out there that cannot possibly be used for OrderedDict.__init__, but which are trivial for every other use case assuming that OrderedDict.__init__ has already been solved. That's why giving OrderedDict.__init__ as the primary use case is a mistake.
The syntax seems pretty obvious:
def kwargs(mapping_constructor): def deco(fn): fn.kwargs_mapping_constructor = mapping_constructor return fn return deco
@kwargs(OrderedDict) def foo(a, b, *args, **kwargs): pass
That's an interesting concept. It would certainly address the most common need I see for better OrderedDict support in the language.
Handling this at the calling site is a bit harder, but still not that hard.
I don't see how this would require changes to the calling site. Can you elaborate?
Sorry, I think I wasn't clear enough here. For you, as a Python coder, the only change is in defining functions, not calling them. But for the interpreter, there's obviously a change in CALL_FUNCTION (and friends) or somewhere nearby—wherever it builds a dict out of the keyword arguments that don't match named parameters, it instead has to look up and use the mapping constructor. I meant to talk about the interpreter level, but it ended up sounding like I was talking about the user level. Anyway, it looks like the simplest implementation in CPython is about 5 one-liner changes in ext_do_call (http://hg.python.org/cpython/file/3.3/Python/ceval.c#l4294) and update_keyword_args (http://hg.python.org/cpython/file/3.3/Python/ceval.c#l4171). In PyPy, if I remember correctly, it would be a 1-liner change in the standard argument factory function. I don't know about other implementations, but I doubt they'd be much worse. Thinking about the implementation raises some points about the interface. CPython (with the simplest changes) will always call your constructor with no parameters, and then set the items one by one. So, maybe don't require any more than empty-construction, __setitem__, and __getitem__, instead of a fancy constructor and the full MutableMapping protocol. Alternatively, PyPy's argument factory is already more flexible; maybe require that as part of the language?
On 16/05/13 07:27, Andrew Barnert wrote:
The first thing you think of is, "Oh, I just need to use an OrderedDict.". Well, technically yes, except there's no convenient way to instantiate an OrderedDict with more than one element at a time.
There's not *that* much difference between writing: OrderedDict(a=1, b=2, c=3) # doesn't work as expected and OrderedDict([('a', 1), ('b', 2), ('c', 3)]) that justifies creating OrderedDicts the hard way: d = OrderedDict() d['a'] = 1 d['b'] = 2 d['c'] = 3 as earlier suggested. Yes, it would be nice to use the first version, but it is a Nice To Have, not a Must Have.
So now you're back to rewriting calling sites into order-preserving lists-of-tuples again. Which is why I think the OrderedDict.__init__ case is in and of itself compelling. ;-)
OrderedDicts are important, but they aren't important enough to impose their requirements on the entire language. * I've already mentioned the risk of performance costs. Most applications of dicts do not care about order. Imposing performance costs on those applications in order to satisfy a few that do is probably a bad trade off, unless those costs are trivial. * We're not just talking about CPython here. Anything that is part of the language must be applicable to all Python implementations, not just the big four (CPython, Jython, IronPython, PyPy) but all the little ones as well. Even if CPython adopts Raymond Hettinger's dict optimization that keeps order as a side-effect, do we really want to make that a language requirement? (I'm not saying that we should, or shouldn't, but only that the stakes are bigger than just CPython.) What's important is not just the magnitude of the changes necessary to make kwargs ordered, but the possible implementations that may be ruled out. It is possible for a language to over-specify features as well as under-specify, and we should be cautious about doing so. [...]
Or consider this small change to the rules for passing **kwargs. Currently, Python guarantees to build a new dict-like object out of anything you pass, then update it. What if Python instead guaranteed to build a new mapping of the same type (e.g., via copy.copy), then update it in order? Then you could just do this:
create_element('img', alt="Some cool stuff.", src="coolstuff.jpg", **OrderedDict())
I can't help but feel that if order of keyword arguments is important, you should take an ordered dict as an explicit argument rather than accept keyword arguments. Given: def create_element(tag, alt, src): pass even if kwargs become ordered in some way, how will your create_element function distinguish between these two calls? create_element('img', alt='something', src='something.jpg') create_element('img', src='something.jpg', alt='something') I don't believe it can. Hence, when order is important, you cannot use keyword arguments to provide arguments *even if kwargs are ordered*. But if you write your function like this: def create_element(tag, mapping): pass and call it like this: create_element('img', OrderedDict([('alt', 'something'), ('src', 'something.jpg')])) then you can get order for free. Yes, it's a little less convenient to use a list of tuples than nice keyword syntax, but that's a solution that doesn't impose any costs on code that doesn't care about ordering. For what it's worth, I'm +0 on specifying that dicts must keep creation order unless items are deleted. I'm -1 on making OrderedDict the default dictionary type. -- Steven
From: Steven D'Aprano
On 16/05/13 07:27, Andrew Barnert wrote:
The first thing you think of is, "Oh, I just need to use an OrderedDict.". Well, technically yes, except there's no convenient way to instantiate an OrderedDict with more than one element at a time.
There's not *that* much difference between writing:
You're quoting me quoting someone else (Don Spaulding) here. The problem may be that I'm using the horrible Yahoo webmail client, which is especially bad at indenting replies to rich-text emails, and therefore it's hard for you to tell what's going on? But I think this led to some confusion farther down.
Or consider this small change to the rules for passing **kwargs. Currently,
Python guarantees to build a new dict-like object out of anything you pass, then update it. What if Python instead guaranteed to build a new mapping of the same type (e.g., via copy.copy), then update it in order? Then you could just do this:
create_element('img', alt="Some cool stuff.",
src="coolstuff.jpg", **OrderedDict())
I can't help but feel that if order of keyword arguments is important, you should take an ordered dict as an explicit argument rather than accept keyword arguments.
I tossed out as wide a variety of solutions as I could come up with, to show that almost anything you come up with is either only works if it doesn't have to work for OrderedDict.__init__, or at least gets a lot easier if it doesn't have to work for OrderedDict.__init__. The one you're replying to is the last, and probably worst, of those spitballed ideas. I certainly wasn't proposing that we actually do it. Anyway, my point is this: If the goal is to solve ordered kwargs, don't try to make that solution work for OrderedDict.__init__ (so we can use OrderedDict as part of the solution). Alternatively, if the goal is to improve OrderedDict construction, don't try to do so by solving ordered kwargs. To be clear, going over my spitballed ideas and those earlier in the thread: I'm -0 on having a map constructor attribute/slot for functions, -0.5 on a PyPy-style argument factory attribute/slot, -0 on adding odict literals with some new syntax, -1 on adding odict literals if they look like Python 3.3 OrderedDict constructor calls, -1 on requiring kwargs to preserve the type it's handed, -1 on allowing default values for *args and **kwargs, -1 on making OrderedDict the default dictionary type, -1 on making it the type for kwargs, -0.5 on specifying that dicts must keep creation order unless items are deleted, -1 for making that change in CPython without specifying it as part of the language.
On 16/05/13 15:51, Andrew Barnert wrote:
From: Steven D'Aprano
Sent: Wednesday, May 15, 2013 8:16 PM
On 16/05/13 07:27, Andrew Barnert wrote:
The first thing you think of is, "Oh, I just need to use an OrderedDict.". Well, technically yes, except there's no convenient way to instantiate an OrderedDict with more than one element at a time.
There's not *that* much difference between writing:
You're quoting me quoting someone else (Don Spaulding) here. The problem may be that I'm using the horrible Yahoo webmail client, which is especially bad at indenting replies to rich-text emails, and therefore it's hard for you to tell what's going on? But I think this led to some confusion farther down.
Ah, I knew that, but I lost the attribution to Don. Sorry about that. -- Steven
On 05/15/2013 08:16 PM, Steven D'Aprano wrote:
I don't believe it can. Hence, when order is important, you cannot use keyword arguments to provide arguments *even if kwargs are ordered*. But if you write your function like this:
def create_element(tag, mapping): pass
and call it like this:
create_element('img', OrderedDict([('alt', 'something'), ('src', 'something.jpg')]))
then you can get order for free. Yes, it's a little less convenient to use a list of tuples than nice keyword syntax, but that's a solution that doesn't impose any costs on code that doesn't care about ordering.
Which 'free' are you talking about? Because if the solution requires extra typing and extra visual clutter, it's not free. -- ~Ethan~
On 16/05/13 23:50, Ethan Furman wrote:
On 05/15/2013 08:16 PM, Steven D'Aprano wrote:
I don't believe it can. Hence, when order is important, you cannot use keyword arguments to provide arguments *even if kwargs are ordered*. But if you write your function like this:
def create_element(tag, mapping): pass
and call it like this:
create_element('img', OrderedDict([('alt', 'something'), ('src', 'something.jpg')]))
then you can get order for free. Yes, it's a little less convenient to use a list of tuples than nice keyword syntax, but that's a solution that doesn't impose any costs on code that doesn't care about ordering.
Which 'free' are you talking about? Because if the solution requires extra typing and extra visual clutter, it's not free.
Free like a puppy :-) You make a good point. Perhaps "free" was a bad choice of words. Rather, let me say that if you need ordered keyword arguments, you can have them *right now* without waiting for the day when you can drop support for everything older that Python 3.4 (or whatever version gives you order-preserving kwargs). -- Steven
Forgive me if this has been mentioned before (i don't think it has) but how
about an option somehow to take the list of **kwargs as an
association-list? I am approaching this from a point of view of "why am I
putting everything into a hashmap just to iterate over it later", as you
can see in the way the namedtuple constructor is implemented:
http://docs.python.org/2/library/collections.html#namedtuple-factory-functio...
This may be rather out-there, and I'm not sure if it'll speed things up
much, but I'm guessing iterating over an assoc list is faster than
iterating over anything else. Building an assoc list is also probably
faster than building anything else and it's also the most easily
convertible (either to OrderedDict or unordered dict) since it preserves
all information.
-Haoyi
On Fri, May 17, 2013 at 5:07 AM, Steven D'Aprano
On 16/05/13 23:50, Ethan Furman wrote:
On 05/15/2013 08:16 PM, Steven D'Aprano wrote:
I don't believe it can. Hence, when order is important, you cannot use keyword arguments to provide arguments *even if kwargs are ordered*. But if you write your function like this:
def create_element(tag, mapping): pass
and call it like this:
create_element('img', OrderedDict([('alt', 'something'), ('src', 'something.jpg')]))
then you can get order for free. Yes, it's a little less convenient to use a list of tuples than nice keyword syntax, but that's a solution that doesn't impose any costs on code that doesn't care about ordering.
Which 'free' are you talking about? Because if the solution requires extra typing and extra visual clutter, it's not free.
Free like a puppy :-)
You make a good point. Perhaps "free" was a bad choice of words. Rather, let me say that if you need ordered keyword arguments, you can have them *right now* without waiting for the day when you can drop support for everything older that Python 3.4 (or whatever version gives you order-preserving kwargs).
-- Steven
______________________________**_________________ Python-ideas mailing list Python-ideas@python.org http://mail.python.org/**mailman/listinfo/python-ideashttp://mail.python.org/mailman/listinfo/python-ideas
On May 18, 2013, at 19:13, Haoyi Li
Forgive me if this has been mentioned before (i don't think it has) but how about an option somehow to take the list of **kwargs as an association-list?
The question is, how would you _specify_ that option? The best way I can think of is a function attribute, with a decorator to set the attribute. Similar to my earlier suggestion for a function attribute that takes a constructor callable. Your idea is simpler conceptually, but it's not much simpler to use, and it's actually more complicated in implementation. The existing function calling machinery explicitly uses mapping functionality, at least in CPython and PyPy. Not that it would be _hard_ to rewrite it around a sequence instead, but it would still be harder than not doing so.
I am approaching this from a point of view of "why am I putting everything into a hashmap just to iterate over it later", as you can see in the way the namedtuple constructor is implemented:
http://docs.python.org/2/library/collections.html#namedtuple-factory-functio...
This may be rather out-there, and I'm not sure if it'll speed things up much, but I'm guessing iterating over an assoc list is faster than iterating over anything else. Building an assoc list is also probably faster than building anything else and it's also the most easily convertible (either to OrderedDict or unordered dict) since it preserves all information.
But you're forgetting that the all existing kwargs code would get slower if we first built a list of pairs and then constructed a dict from it, as would any new code that wants to do lookup by name. So, you're slowing down the 90% case to speed up the 10% case. Also, the existing functionality is something like this pseudocode: kwargs = dict(starstarargs) for arg, val in zip(namedargs, namedvals): if arg not in f.paramnames: kwargs[arg] = val (I linked to the actual CPython and PyPy code earlier in the thread.) So, if performance actually matters, presumably you're going to hash the names anyway to do that in check, at which point the biggest cost of using a dict is already incurred.
The question is, how would you _specify_ that option?
This seems like the perfect use case for function annotations, or a decorator. I imagine both cases would look rather pretty def func(**kwargs: ordered): ... @ordered_kwargs def func(**kwargs): ... I'm not sure if this is a bad idea for other reasons (e.g. decorators/annotations being reserved for library code rather than core language features) but it does look right intuitively: you are annotating the function or the kwarg to change it's behavior. Here's another thought: macros https://github.com/lihaoyi/macropy would be able to pretty trivially give you a syntax like:
odict(a = 1, b = 2) OrderedDict([('a', 1), ('b', 2)]) odict(b = 2, a = 1) OrderedDict([('b', 2), ('a', 1)])
or
o%dict(a = 1, b = 2) OrderedDict([('a', 1), ('b', 2)]) o%dict(b = 1, a = 1) OrderedDict([('b', 2), ('a', 1)])
for ordered dict literals. It wouldn't work for generally adding
orderliness to other functions, since the macro won't know which bindings
are named arguments and which bindings are **kwargs, but it also looks
really pretty. Probably not something you'd want to put in the std lib, but
it's fun if you want to try out the syntax in your own projects.
-Haoyi
On Tue, May 21, 2013 at 12:40 PM, Andrew Barnert
On May 18, 2013, at 19:13, Haoyi Li
wrote: Forgive me if this has been mentioned before (i don't think it has) but how about an option somehow to take the list of **kwargs as an association-list?
The question is, how would you _specify_ that option?
The best way I can think of is a function attribute, with a decorator to set the attribute. Similar to my earlier suggestion for a function attribute that takes a constructor callable.
Your idea is simpler conceptually, but it's not much simpler to use, and it's actually more complicated in implementation.
The existing function calling machinery explicitly uses mapping functionality, at least in CPython and PyPy. Not that it would be _hard_ to rewrite it around a sequence instead, but it would still be harder than not doing so.
I am approaching this from a point of view of "why am I putting everything into a hashmap just to iterate over it later", as you can see in the way the namedtuple constructor is implemented:
http://docs.python.org/2/library/collections.html#namedtuple-factory-functio...
This may be rather out-there, and I'm not sure if it'll speed things up much, but I'm guessing iterating over an assoc list is faster than iterating over anything else. Building an assoc list is also probably faster than building anything else and it's also the most easily convertible (either to OrderedDict or unordered dict) since it preserves all information.
But you're forgetting that the all existing kwargs code would get slower if we first built a list of pairs and then constructed a dict from it, as would any new code that wants to do lookup by name. So, you're slowing down the 90% case to speed up the 10% case.
Also, the existing functionality is something like this pseudocode:
kwargs = dict(starstarargs) for arg, val in zip(namedargs, namedvals): if arg not in f.paramnames: kwargs[arg] = val
(I linked to the actual CPython and PyPy code earlier in the thread.)
So, if performance actually matters, presumably you're going to hash the names anyway to do that in check, at which point the biggest cost of using a dict is already incurred.
On 5/23/2013 12:36 PM, Haoyi Li wrote:
The question is, how would you _specify_ that option?
This seems like the perfect use case for function annotations, or a decorator. I imagine both cases would look rather pretty
def func(**kwargs: ordered): ...
Guido has more or less rejected annotations because checking for an anotation would slow down every function call for the benefit of very few.
@ordered_kwargs def func(**kwargs): ...
Returning a function with a custom ordered kwargs .__call__ method would not affect normal functions. func.__class__ cannot be changed from Python code (non-heap type) but I don't know about from C. In any case, the attributes (components) of func could be fed to an okw_function (ordered keyword function) class to produce an new object. The decorator approach immediately fails for a system without the decorator. Since it should only be used for funcs that require the ordering, I think that would be appropriate. tjr
On 16/05/13 07:35, Don Spaulding wrote:
So someone else has built a system around an XML parser that doesn't know that sibling elements aren't guaranteed to appear in any particular order.
Are you *sure* those elements aren't required to appear in a particular order? It depends on how the DTD is written. The parser may actually be doing the right thing based on the DTD it was given or based on. From http://www.w3schools.com/dtd/dtd_elements.asp: Elements with Children (sequences) Elements with one or more children are declared with the name of the children elements inside parentheses: <!ELEMENT element-name (child1,child2,...)> When children are declared in a sequence separated by commas, the children must appear in the same sequence in the document. -- Greg
On Tue, May 14, 2013 at 12:53:53PM -0700, Jonathan Eunice wrote:
But from an app developer’s point of view, ordering is a basic, essential property. [...] So I propose that kwargs, at least, default to an ordered mapping rather than a pure hash mapping.
Speak for yourself. I don't think it is, and while having a fixed order is sometimes useful, often it is not necessary. Thinking about my code, I cannot think of even one function or method which would get a benefit from having kwargs be ordered. Frankly, with the exception of OrderedDict itself, if your functions would like to treat kwargs args differently based on their order, e.g. func(a=1, b=2) vs func(b=2, a=1), I think your design may be broken. Keeping things ordered imposes a performance cost. I think you would need to demonstrate that the advantage of having kwargs be an ordered dict for the cases where it matters outweighs the cost for the cases where it doesn't matter. If somebody demonstrates that the cost of shifting to an ordered dict is minimal, and the advantage is non-trivial, then and only then would I support the idea.
Historically, sort features were usually unstable because that’s easier to implement and faster to run. Over time, stable sort has become the norm,
I don't think that is a particularly good analogy. Stable sorting is intuitively correct. Treating keyword args differently according to their order is intuitively the wrong thing to do, at least most of the time. -- Steven
On 15 May 2013 09:34, Steven D'Aprano
I don't think that is a particularly good analogy. Stable sorting is intuitively correct. Treating keyword args differently according to their order is intuitively the wrong thing to do, at least most of the time.
The argument *for* an ordered kwargs however is that same one that was used for Enums iterating in definition order by default - it's an ordering that can't be recovered once it's lost. However, it's not a property that I think is absolutely necessary for kwargs and we shouldn't lose performance to gain that property, but there have been times when I would have liked it. Barry created a new dict implementation a while back that as a side-effect retained insertion order so long as no keys were removed. That would be suitable IMO for kwargs as a guarantee - definition order so long as nothing has been removed. It was discussed and there was the suggestion to actively break this functionality in order to prevent people relying on it. I'm not sure what the end result of the discussion was off the top of my head. Tim Delaney
On Tue, May 14, 2013 at 5:36 PM, Tim Delaney
On 15 May 2013 09:34, Steven D'Aprano
wrote: I don't think that is a particularly good analogy. Stable sorting is intuitively correct. Treating keyword args differently according to their order is intuitively the wrong thing to do, at least most of the time.
The argument *for* an ordered kwargs however is that same one that was used for Enums iterating in definition order by default - it's an ordering that can't be recovered once it's lost.
However, it's not a property that I think is absolutely necessary for kwargs and we shouldn't lose performance to gain that property, but there have been times when I would have liked it.
Barry created a new dict implementation a while back that as a side-effect retained insertion order so long as no keys were removed. That would be suitable IMO for kwargs as a guarantee - definition order so long as nothing has been removed. It was discussed and there was the suggestion to actively break this functionality in order to prevent people relying on it. I'm not sure what the end result of the discussion was off the top of my head.
There was also some conversation at the pycon sprints this year about if keyword arguments could use an ordered dict or not but I wasn't paying enough attention to that to be able to give a summary of what was discussed. My gut feeling is that it'd add overhead even though I would find it useful at times. -gps
On Tue, May 14, 2013 at 7:34 PM, Steven D'Aprano
On Tue, May 14, 2013 at 12:53:53PM -0700, Jonathan Eunice wrote:
But from an app developer’s point of view, ordering is a basic, essential property.
Speak for yourself. I don't think it is, and while having a fixed order is sometimes useful, often it is not necessary.
The fact that it is often useful -- if only for debugging and testing -- can make it seem like a basic property that shouldn't be sacrificed without a reason. Think of the contortions that dict code (prior to the Denial Of Service scare) went through to maintain a stable (albeit arbitrary) order. I also suspect I'm not the only one who looks at the self.(var) = (var) of an __init__ function and feels that the arguments are really more of an association-list, so that creating a map was just wasted work. I do NOT propose to fix this code smell in the general case, though.
Frankly, with the exception of OrderedDict itself, if your functions would like to treat kwargs args differently based on their order, e.g. func(a=1, b=2) vs func(b=2, a=1), I think your design may be broken.
I agree. But the line between "broken" and "easier to debug" isn't always bright. (That said, if kwargs in particular were essentially ordered, I would want to allow repeats, as do the web mappings. I'm not sure that would be a net positive for readability.)
Historically, sort features were usually unstable because that’s easier to implement
Not really, for the more obvious algorithms. But those aren't the fastest.
and faster to run. Over time, stable sort has become the norm,
Stable sorting is intuitively correct.
My intuition is that if two objects are equal, it shouldn't matter what order they come in. Preference for a stable sort only comes after lots of experience with data flows involving (or abusing) multi-step sorts. -jJ
participants (14)
-
Amaury Forgeot d'Arc
-
Andrew Barnert
-
Daniel Holth
-
Don Spaulding
-
Ethan Furman
-
Greg Ewing
-
Gregory P. Smith
-
Haoyi Li
-
Jim Jewett
-
Jonathan Eunice
-
random832@fastmail.us
-
Steven D'Aprano
-
Terry Jan Reedy
-
Tim Delaney