Deprecate "old-style iteration protocol"?

Two different people answered you the same way, so if you were misunderstood, what you meant wasn’t “pretty clear”.

But I don’t think you were misunderstood. I think you were, and still are, just incorrect in your claim that classes (with or without __getitem__ are “assumed to be infinite lazy sequences by default”.

The interpreter makes no assumption about your class being lazy or infinite. The fact that your class behaves as a lazy infinite sequence is because you programmed it to behave as a lazy infinite sequence (as well as an infinite mapping).

If the user of your identity class were to write this:


obj = identity_dict()  # Your class.

key = random.random()

while True:

    try:

        print(obj[key])

    except LookupError:

        break

    key = random.random()

it would still loop forever and never terminate. And the nature of the identity dict is such that there is nothing you can do to prevent this infinite loop except tell your users “Don’t do that!” and make them aware that it is a lazy, infinite mapping.

At least with iteration (for-loops or list) you can disable that completely by setting the iter dunder to None.

It isn’t lazy and infinite “by default”, it is lazy and infinite because you programmed it to be that way.

Well yeah.

How else are you going to signal that the sequence (or mapping) doesn’t have an index/key if you don’t raise an exception?

For real collections (sequences or mappings) you have some actual data structure with a finite size, so this issue doesn’t come up. When you run out of data, you get a LookupError (IndexError or KeyError).

Your mapping doesn’t have actual data. It lazily simulates fake data, and does so without terminating. So what did you expect to happen if the caller repeatedly looks up indexes/keys over and over again?

If every key always succeeds then that implies that it is infinite and lazy.

If it was not your intention to write an infinite, lazy collection then it is your code that is buggy, not the language. If it was your intention, then congratulations, you succeeded, and the Python interpreter did exactly what you told it to do, which was to loop forever.

Right. That’s because iteration in Python can use two different protocols, with iter taking precedence.

Your class is not considered a Sequence. isinstance(identity_dict(), collections.abc.Sequence) returns False.

But your class behaves sufficiently like a sequence in this regard, because you programmed it to behave like a sequence.

In other words, your class might not swim or fly like a sequence, but it quacks like a sequence. For a task like iteration that only requires quacking, your class might as well be a sequence.

This sort of duck-typing is built deep in the Python execution model. If you don’t want it, I’m afraid you are using the wrong language :frowning:

Dunders are used by the interpreter to implement certain behaviours. If you write the dunder, you are responsible for that behaviour. The interpreter isn’t assuming anything – you have explicitly written the dunder to cause your class to behave in the way that it then behaves.

As far as your issues with the POLA, we’re going to simply have to disagree on this one.

You wrote a class with a dunder used to define sequence behaviour, and then were surprised that your class behaved like a sequence. I was not.

Right. And that is why arguments from the POLA are very weak when it comes to APIs, beyond such obvious and trivial examples that (let’s say) a function called “print” should print.

APIs consist of much more than just function names. Knowledge of protocols can be surprising if you don’t know the protocol, but that is not a violation of POLA. That’s just lack of knowledge.

This is just wrong. __getitem__ does not imply a default __iter__ implementation.

This is the second time you have made that wrong claim about a default __iter__, please stop repeating that misinformation. Your class has no __iter__ method, the interpreter does not add one, and iteration using __iter__ is only one of two ways that iteration is defined in Python.

Just as the str() builtin falls back on __repr__ when __str__ doesn’t exist, and the not-equal operator falls back on __eq__, and most operators have a reversed __rop__ method. You cannot assume that operations in Python only use a single dunder.

No, but then I am something of a mathematician of sorts, so it wouldn’t surprise me for multiplication to fall back on addition. That is the most natural thing in the world.

I beg to differ. It is not surprising, because it does intuitively follow from getting an item.

The most simple, natural, intuitive form of iteration is:

  • get the first item (index 0 in Python);

  • get the second item;

  • get the third item;

  • get the fourth item;

etc, halting when there are no more items.

It is a critical distinction with obvious consequences, not the least of which is that testing for iteration by looking only for a iter dunder is not sufficient.

Iteration is not controlled only by the presence of a iter dunder. If you thought it was, you were wrong. It is as simple as that.

You lacked knowledge about the design of Python. Your lack of knowledge doesn’t mean that the interpreter is wrong, or that we should break working code to bring Python back into line with your incorrect assumption about iteration. It just means you lacked knowledge.

Now you know better. Congratulations. You are a more knowledgeable Python programmer today than you were two days ago.

In a practical sense, it is highly unlikely that depreciation would make it into 3.11, so you shouldn’t expect it before 3.12. At which point it will likely be silent depreciation. Unless you run Python with all silent warnings enabled, which hardly anyone does, you probably wouldn’t see the warning until 3.13 or 3.14.