On Dec 26, 2015, at 22:22, Nick Coghlan <ncoghlan@gmail.com> wrote:

On 27 December 2015 at 13:07, Andrew Barnert via Python-ideas
<python-ideas@python.org> wrote:
Anyway, the main argument for eliminating the old-style sequence protocol is that, unlike most other protocols in Python, it can't actually be checked for (without iterating the values). Despite a bunch of explicit workaround code (which registers builtin sequence types with `Iterable`, checks for C-API mappings in `reversed`, etc.), you still get false negatives when type-checking types like Steven's at runtime or type-checking time, and you still get false positives from `iter` and `reversed` themselves (`reversed(MyCustomMapping({1:2, 3:4}))` or `iter(typing.Iterable)` won't give you a `TypeError`, they'll give you a useless iterator--which may throw some other exception later when trying to iterate it, but even that isn't reliable).


There's an additional option we can consider, which is to move the
backwards compatibility fallback to type creation time, rather than
method lookup time.

Sure, that's possible, but why? It doesn't make it any easier to add the rule "__iter__ is None blocks fallback". It doesn't make it easier to eventually remove the old-style protocol if we decide to deprecate it (if anything, it seems to make it harder, by adding another observable difference). It might make it easier to write a perfect Iterable ABC, but making a pure-Python stdlib function simpler at the cost of major churn in the C implementation of multiple builtins and C API functions (and similar for other implementations) doesn't seem like a good tradeoff.

Unless it would be a lot simpler than I think? (I confess I haven't looked too much into what type() does under the covers, so maybe I'm overestimating the risk of changing it.)

However, while I think those changes would clean up some quirky edge
cases without causing any harm, even doing all of that still wouldn't
get us to the point of having a truly *structural* definition of the
difference between a Mapping and a Sequence. 

Agreed--but that wasn't the goal here. The existing nominal distinction between the two types, with all the most useful structurally-detectable features carved out separately, is a great design; the only problem is the quirky edge cases that erode the design and the workarounds needed to hold up the design; getting rid of those is the goal.

Sure, being able to structurally distinguish Mapping and Sequence would probably make that goal simpler, but it's neither necessary nor sufficient, and is probably impossible. 

For example, OrderedDict
defines all of __len__, __getitem__, __iter__ and __reversed__
*without* being a sequence in the "items are looked up by their
position in the sequence" sense.

Sure, but that just means Sequence implies Reversible (and presumably is a subtype of Reversible) rather than the other way around. There's still a clear hierarchy there, despite it not being structurally detectable.

These days, without considering the presence or absence of any
non-dunder methods, the core distinction between sequences,
multi-dimensional arrays and arbitrary mappings really lies in the
type signature of the key parameter to__getitem__ et al (assuming a
suitably defined Index type hint):

Even that doesn't work. For example, most of the SortedDict types out there accept slices of keys, and yet a SkipListSortedDict[int, str] is clearly still not a sequence despite the fact that its __getitem__ takes Union[int, Slice[int]] just like a list[str] does. Unless the type system can actually represent "contiguous ints from 0" as a type, it can't make the distinction structurally. But, again, that's not a problem. I don't know of any serious language that solves the problem you're after (except maybe JS, Tcl, and others that just treat all sequences as mappings and have a clumsy API that everyone gets wrong half the time). The existing Python design, cleaned up a bit, would already be better than most languages, and good enough for me.