On Fri, Jul 31, 2020 at 2:56 PM Marco Sulla <Marco.Sulla.Python@gmail.com> wrote:
Yes. Since now dicts are ordered by insertion, also keys, values and items are ordered the same way.

Preservation of oider was codified in version 3.7 (? at some point, anyway). And while it may not explicitly say that the views will present the same order, I haven't heard anyone complain about that in this discussion.

So:
dicts preserve order
the dict_views preserve this same order

Some think that we can't call them "ordered", because somehow that implies other things, like the ability to insert, etc, but while I don't get that argument, fine, let's call them "order preserving".

So in terms of what is defined by the language, and what is technically possible, dict views could be indexed and sliced with clear semantics. Most other Sequence operations are not possible, so no, dicts are not Sequences, and the dict views aren't either. So we won't register them with the Sequence ABC, no problem.

So why not add this? Form what I can tell from this thread, there are these reasons:

1) Because adding ANYTHING new is taking on a commitment to preserve it in the future, and it's more code to write, maintain, and document. So regardless of any other argument, it shouldn't be added without a reasonable use case(s) -- what's reasonable is subjective, of course.

2) Because it could be an "attractive nuisance" while dicts are order preserving, their internal structure is such that you cannot find the nth item without iterating through n items, making access O(n), rather than O(1) for access by key or access of the usual Sequences -- Folks expect numerical indexing to be O(1), so it could be tricky. However, the only other way to get an n'th item is to copy everything into a Sequence, which is a lot slower, or to use islice or next() to do the order N access by hand. So this is an attractive nuisance in the use case of wanting to take multiple items by index, in which case, making a sequence first would be better than directly accessing the dict_view object.

3) dicts are not Sequences, they are Mappings, so they shouldn't have Sequence features. dict_views are Sets, not Sequences, so they shouldn't have Sequence features.

Point 3) I may have misrepresented it, it was explained in a lot more detail, but I'm pretty sure that's what it comes down to. But I'm going to talk about my understanding of the point, which is pretty much how I wrote it. If I really did misrepresent it, then feel free to kibitz some more....

It seems like I have a different philosophy about typing and duck typing than some in this converstaion. I appreciate the ABCs, and that they clearly nail down what types need to be in order to "BE" one of the ABCs -- and see how this is useful. But I don't see it as restrictive. An object is not a Sequence unless it fully conforms to the Sequence ABC, sure. But that doesn't mean an object can't have some, but not all, of the behavior of a Sequence without having all the rest. In this example, implementing integer indexing and slicing on dict_views would not make them a Sequence, but that doesn't mean you can't do it. It's also the case that any type can BE a particular ABC, and still have other functionality. So, in this case, the dict_views are Sets, but we can add other things to them of course.

So on to "duck typing" -- the term is comes from the aphorism: "If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck" (https://en.wikipedia.org/wiki/Duck_test).

So that pretty much maps to ABCs: the ABC for a duck specifies what a duck is -- it's something that looks, swims, and quacks like a duck.

But I've always taken a more flexible view on duck typing -- if I want to know if it's a duck, yes, I need the ABC. But I don't usually care if it's a duck -- I care if it does the one or two things I need it to do. So if I need a quacker, then anything that quacks like a duck is fine with me, even if it can't swim.

Bringing this back to this concrete example:

One of the use cases brought up was being able to choose a random item from a dict. You can't pass a dict (or dict_views) to random.choice. But not because a dict isn't a Sequence, but because they aren't subscriptable:

In [14]: random.choice(d.keys())                                                
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-14-76483ebbc518> in <module>
----> 1 random.choice(d.keys())

~/miniconda3/envs/py3/lib/python3.8/random.py in choice(self, seq)
    289         except ValueError:
    290             raise IndexError('Cannot choose from an empty sequence') from None
--> 291         return seq[i]
    292
    293     def shuffle(self, x, random=None):

TypeError: 'dict_keys' object is not subscriptable

Everyone on this thread knows this, but to be explicit, anything with a length and that is subscriptable with integers between 0 and that length can be used with random.choice. I think this is the minimal object that works:

In [23]: class Dummy:
    ...:     def __len__(self):
    ...:         return 1000
    ...:     def __getitem__(self, idx):
    ...:         return idx
    ...:                                                                        

In [24]: d = Dummy()                                                            

In [25]: random.choice(d)                                                      
Out[25]: 194

THIS is what I think is the key to Python's Dynamic, Duck Typing, and it's been there from the beginning, and I really like it.

There's been a lot of movement lately toward static typing, but I sure hope it doesn't get enforced in the standard library in places like these.

So: I really don't understand argument (3) above, it is at odds with what I like about Python.

So back to this feature request:

I can understand argument (2) -- the attractive nuisance, but I don't think it's a big deal. We simply can't avoid having less that optimum ways to do certain things, as long as you can still easily wrap list() or tuple() around the dict views when you need that, I don't see a problem.

Which brings us to (1) -- is this a useful enough feature to justify the churn? And the answer to that is probably not, as far as I can tell, there have been only two use cases presented:

selecting a random key from a dict

selecting an ordered subset of a dict (a slice)

If that's all we've come up with in this lengthy thread, it's probably not worth it -- after all, both of those can be efficiently accomplished with a little help from itertools.islice and/or next().

Which does bring us to a final point, and the reason why if it were only up to me, I'd probably add indexing to dict_views: I find the explicit use of iter(0 and next() and itertools.islice() pretty heavyweight, difficult, and, well, ugly, compared to indexing and slicing. Others disagree.

But I think we all agree that those tools are less newbie-friendly -- but they should be learned at some point, so maybe that's OK (and there is always the wrap it with a list approach, which is pretty newbie friendly)

Sorry for the long post that concludes with "nope, we're not going to do this" :-)

What I have not understood well is when you need to index a dict by position or slice it.

see the two use cases above -- that's all we have, but they ARE real use cases, if the only two that have been identified.

-CHB


--
Christopher Barker, PhD

Python Language Consulting
  - Teaching
  - Scientific Software Development
  - Desktop GUI and Web Development
  - wxPython, numpy, scipy, Cython