Interpret "for i in n" as "for i in range(n)" if n is an int

I have, for example for subsequence testing:

def is_subseq(x, y):
    it = iter(y)
    return all(c in it for c in x)
2 Likes

Give me an example of one. What does it actually mean to iterate over such a thing, and what would it mean to ask if it contains a thing?

This is really off topic now. A simple example would be a generator used to implement something like a coroutine that accepts stuff with .send(). For that, iteration might not even be fully sensible, but containment checks would be completely nonsensical. Or infinite generators, for which the containment check can never produce False, for example an infinite RNG generator (or itertools.count() or .cycle()).

Note that I didn’t say that containment makes sense for those things. I just said it doesn’t make sense for those to implement any kind of containment check (since they aren’t a collection). But giving people footguns that might work with limited testing, but don’t actually work in general not really the style of python.

If you want to continue to argue this, at least do this in DMs, a new post or ask admins to split this off.

The naive implementation of that containment check can never return False, but for both of those, it is very well-defined as to whether or not it contains something. Just like a range object tests for containment without iteration, you could quickly and easily see whether a count contains a specific value (ie if it would ever yield it in the future).

Generators aren’t containers, so that is one broad type of counterexample. However, I would say that coroutines merely happen to be implemented using generators, and aren’t a good example (they’re really async functions, and don’t follow the same rules). When you’re working with a generator, the idea of “iterate over this” is being cheated somewhat.

Thank you for completely missing the point of my argument. I am no interested in further discussion.

I don’t think you can implement it in such a way that it works in general. Seems only semi-decidable. For an arbitrary object, you can never be sure it doesn’t occur later.

That is true. However, I was describing the parallel between iteration and containment as an “elegance of design”, which is a matter of intent. Any object which behaves in a container-like way should normally implement its __iter__ and __contains__ such that this would hold true. Anything that DOESN’T do this is probably not a container. I cannot at the moment think of anything that is both iterable and supports containment checks, and doesn’t at least broadly follow this pattern. (Unless someone’s made a generator that has a contains method, which would surprise me GREATLY.)

One of neigboring topics is about defining len() for integer as returning the number of digits:

To be consistent with this idea, iterating an integer should produce its digits. But first we need to discuss some design details:

  • Should it iterate from less significant digits to more significant digits or vice versa? Should the order be locale-dependent?
  • Should it produce characters or integers?
1 Like

To motivate / defend my original request a bit, though I want to preface it by saying I am a mere user of Python and have not spent any time deeply engaging with either the philosophy behind the language design or the internals of how things are currently implemented:

Iterating through integers from 0 up to some maximum is a very common operation, especially in a domain like data science when you are working with tensors / multidimensional numpy arrays, and you iterate over indices. Not all such operations are vectorized, and sometimes the programmer may actively avoid the vectorized solution (e.g. if using numba to accelerate numpy operations).

However, it’s not just data science, if you search for the “for * in range(*)” in your favorite libraries I’m sure you’ll find many uses (I showed some stdlib examples above). I view this as supportive of it being a common operation.

Strictly from a “simplicity of language” perspetive, if you want to count to 10, the two elements that fully define this operation are “counting” and “10” (together with some consensus about where the counting starts (0) and whether or not the last element is included). Yet, to ask Python to perform this common operation, we have to take a detour via another function (range). Instead of saying “count to 10” we are saying something closer to “iterate through the elements from the list that you can form from 0 to 10” which is more awkward. Of couse, in practice it’s just one more word (range) which isn’t so tragic, but still requires use of what I view as a superfluous structure.

So, my two arguments is that this is a somewhat common operation which can only currently be expressed in a somewhat superfluous way, and that doesn’t seem ideal. It’s totally plausible that the medicine isn’t worth the cure in this case, and that the arguments against “fixing” this alleged imperfection are stronger than the benefit. But, I just wanted to give an explicit (perhaps obvious) defense of the ask.

I checked the code that I’m working currently at $work and none of the places I used for in, statements or comprehensions, even used range. So anecdotally I don’t use for i in range(n) very often.
But my project is a network related one which is different from what you’re doing.

“count to N” is very common, but also common is “count from M to N” and “count from M to N by X”.

I think it’d be tricky to come up with nice syntax that is clear and can do all of those things. Syntax that does only one of them is less useful.

2 Likes

I wonder if this idea rests on the heap of FAITHBR:

for i in [5:83:3]:

instead of:

for i in range(5, 83, 3):

Using slicing notation to reduce verbosity.

There were several related FAITHBR ideas:

  • use slice as range for iterating
  • use range as slice for slicing
  • accept None as stop index in range() (means itertools.count())
  • special syntax for slice or range objects
1 Like

What’s FAITHBR?

1 Like

The specific suggestion of using slice notation for creating a list of integers has been rejected (PEP 204) although the reasons for rejection are somewhat vague. Many things have changed since then e.g. range created a list at the time rather than returning a range object, there is now extended unpacking syntax etc.

Using slice syntax specifically for for loops is possibly a different proposal although questions would obviously be raised about what the same syntax should mean outside of a for loop so perhaps you end up back in the same place.

There could be performance optimisations made possible by a dedicated loop over integers syntax or at least some syntax that an optimiser could handle as a special case in loops.

Thanks for sharing this! It’s an interesting perspective, but I think you’ll find a lot of disagreement about what constitutes “simplicity”.

I would phrase what’s happening differently, and it produces a different conceptual result – one in which the current state of affairs is simpler.

The construction we’re talking about isn’t counting. The construction is iteration.

for <expr>:
    <block>

So what’s a valid <expr>? Anything which supports iteration!

What should support iteration? Mostly containers and generators. Sets of things (set, list, etc) and functions or expressions which produce a series of values.

Are integers containers (“set-like”)? Not very much so. They’re comparable, 1 < 3, yes, but do they have things inside of them? 1 in 3? Set theoretic ordinals say yes, but for a general purpose programming language that reads weirdly. We could make them containers, but I think it makes the language less simple. So no.

Are integers generators of some series of values? No. I don’t think this one needs as much explanation.

So integers have failed the test for what probably ought to be iterable! We could make the core language keyword for do double duty, and give it some non-iteration job on integers, but that definitely makes the language less simple.

Luckily, we have a clever way of taking something integer-shaped and turning it into something iterable-shaped. All we need is… range(n).


Some languages have special syntax for walking ranges. The one which leaps to mind is the bash way:

for i in {0..10}; do ...; done

That’s consistent with the Python paradigm that iteration is the primary construct and ranges are just something we iterate over.

Is it simpler? Ehhh… no. It’s just a more arcane way to spell what Python spells as range.

Although Python could add some new or different syntax for what currently exists, why would it be better? Being shorter is only better when we don’t lose clarity. And would it make the language simpler to put more things into it, or more complex?


Simplicity is in the eye of the beholder.
Does Python need a new syntax for counting from 0 to n?
Nah, since it already has a very nice one. It’s:

for i in range(n):
    ...
3 Likes

See Chris’s post earlier in this topic. (Easy to find, btw, FAITHBR is an excellent search term.)

3 Likes

I see, thanks. I was very confused what religion in Brazil had to do with this thread :sweat_smile:

If the suggestion does get implemented, I hereby request the name FAITHBR for it, as an abbreviation for “faith-based range”, meaning one writes for i in 42: and has faith that Python will do the intended thing.

6 Likes

It would be another cool thing from set theory that Python would force/encourage its users to learn or get used to.

Axiom schema of restricted comprehension (with list comprehension) and von Neumann ordinals with (for i in 42 and/or 3 in 42).