On Nov 6, 2015, at 21:00, Stephen J. Turnbull <stephen@xemacs.org> wrote:
Amir Rachum writes:
An ordered collection has the exact same interface as an unordered collection (e.g, dict and OrderedDict), other than a _promise_ of the API that the order in which this collection will be iterated has some sort of meaning (In OrderedDict, it is the order in which keys were added to it.)
I think this is underspecified. One of the main reasons why ordered versions of initially unordered types, especially dict, take so long to standardize is that people (even the Dutch!) disagree about the One Obvious Order to impose. After repeated refusals, only the factions that continue clamoring- and preferably, only one -need be considered.
This ABC would be a tar pit of recriminations between class authors and class clients.
I think you've missed the point of his proposal. It doesn't matter _what_ the order is—insertion order, sorted by __lt__ (possibly with a key function), looked up by a function that maps keys to indices, whatever—only that there is some meaningful order (and that iteration follows that order). If so, your type can declare that it implements Ordered. Of course the user has to know what that order is and what it means in order to do anything with it. In his example, if you use, say, a blist.sortedlist instead of a list for your slots, then you have to pass positional parameters to the initializer in sorted order instead of in source code order. But if that's what makes sense for your code, then great. The reason I'm not sure it's helpful is that "ordered in some way that makes sense to me that you don't know about" isn't really that useful of a thing for you to check for me. After all, I can use a blist.sortedlist('bac') and then pass the initializer values in b-a-c order, or use list('bac') and pass them in a-b-c order. Each of those is at least as easy to screw up, and just as wrong, as using a set('bac'), and his code can't possibly check for the first two, so what's the point of checking for the third? But, whether the proposal is useful or not, it is sufficiently specified.
At the very least, the author's intended comparison function should be introspectable so that client subclasses can include validation functions. (I guess you could just use "lambda slot: list(self.__slots__).index(slot)" as the key function, but that doesn't say much about the conceptual semantics.) Then the presence of the ordering function would provide a way to duck-type abc.Ordered.
How would you define the comparison function for OrderedDict in a way that makes any semantic sense? Or, for that matter, list? The order is the result of a dynamic sequence of operations that nobody's kept track of and that in general can't be recovered. But it doesn't matter why the values are in some particular order, only that they are. Surely OrderedDict and list are perfectly reasonable types for __slots__, despite the fact that their comparison function doesn't exist.
I haven't thought carefully about it, but a method with semantics of "insert next slot at end according to natural order" (named "natural_append"? "ordered_append"? or maybe the existing "append" used by sequences could be given this semantic as well?
You're talking about the API for a mutable sorted collection here. That's an interesting question (and I've written a blog post about it, and had long conversations with the authors of various different sorted collection libs on PyPI), but it's not relevant here. If we wanted to add sorted collection ABCs to the stdlib, that still wouldn't help the OP at all, because things like list and OrderedDict are Ordered in his sense but not sorted in any useful sense. (If you're curious: I think the best answer is to use add. A sorted collection is more like a multiset, at least from the mutation side, than like a list. Append, insert, and __setitem__ are all nonsense; add makes perfect sense. But not everyone agrees with me. If you want to discuss this, you should probably take it off-list, or start a new thread on adding sorted collections ABCs, because they have very little to do with adding an Ordered ABC.)
Nevertheless, I don't think this shold be added to Python yet. I don't think your use case is a valid one for supporting it. I have two reasons:
(1) In principle, record types should not be initialized with positional arguments. I experienced cogniztive dissonance as soon as I saw your __init__():
Here's how the current__init__ method of BasicStruct looks like:
class BasicStruct(object): """Class for holding struct-like objects."""
"with naturally-ordered slots" should be appended.
__slots__ = () # should be extended by deriving classes
def __init__(self, *args, **kwargs):
Yes, we do it in C all the time, and it works fine[1] there. But the semantics are mismatched at the conceptual level, and it is error-prone in practice if the type has more than about 3 slots of differing types
It's worth noting that the C committee agrees with you, which is why they added designated initializers to the language, which are often considered among the best upgrades from C89. Unless you need C89 support or (portable) C++ compatibility (sadly, you usually do need one or the other…), unless you're initializing something with only a few attributes with an obvious-to-the-reader order (like a point, where it's pretty clear that { 1, 2, 3 } is initializing x, y, and z), you use a designated initializer like { .spam=1, .eggs=2, .cheese=3 }. Which looks sort of like Python, but with extra line noise.
If a concrete BasicStruct "wants" to provide a positional constructor, it can use a factory function.
Unfortunately this would be impossible to do automatically in the exactly the cases of interest -- you need the *source code* order of slots, but that's not available for introspection at runtime. So it would violate DRY. I don't consider that an argument for the proposal at the moment, but feel free to try to convince the list (and maybe me too<wink/>) otherwise.) (In fact I consider it the reverse: for Sequences, The Order You See Is The Order You Get, and the DRY violation occurs *because* you are using an inappropriate container for your purpose (ordered slots).
(2) BasicStruct isn't the relevant use case: you can always just document that __slots__ is restricted to ordered finite iterables
It had better be a repeatable one too; after all, iter(['a', 'b']) is an ordered finite iterable, but not a very good candidate for slots. But that's a whole other thread. :)
except string-like ones which use sequences of length 1 to represent individual members, and perhaps trap some of the more obvious mistakes (set and dict and maybe their subclasses).
The use case you need to present is one where a non-Sequence (eg, a set or dict) is the obvious representation for __slots__ in a BasicStruct. Note that __slots__ allows nearly arbitrary finite iterables of strings only because there's no reason not to, and it simplifies the implementation. In Python practice, that is indeed an invitation to experiment with concrete classes that use unordered __slots__ for some reason. But there's no sense that abstract classes that use __slots__ in an abstract way (as yours does) must preserve that generality, and therefore no reason (yet) for Python to help you preserve that generality.
Footnotes: [1] According to the usual definition of "works fine in C": is no more than twice as error-prone as other features of C. Given the existence of pointers....
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/