If I understand correctly, that‘s SequenceNotStr[str] from
But we’re getting off topic with the string discussion. The question was whether integers should be iterable, and there I think the answer is clearly no. I claim it is rather unintuitive. Ask some people what this would do, and I think you won’t get clear answers
for i in 5:
print(i)
AFAICS the iterable idea originated from having mixed types
month = 8 # or None or (7,8)
And the need to coerce int or sequence of int to a sequence before further processing. But I think it’s ok to do
if not isinstance(month, tuple):
month = (month,)
You will have to do this for any other type ( float, custom classes, …) anyway. There’s no benefit in introducing special logic to save this for ints.
That‘s a point of view. I claim that iterable single character / string is the odd ball, but that‘s rooted in that we do not have a concept of character. Characters are just strings of length 1. That has the side effect, that you can iterate over it. It’s not a first class feature intentionally designed. This is not comparable to integers. They are not first and foremost a sequence, which has elements that itself are of the same type.
More generally, what is scalar in your context? Is it only int or also float, a custom class, …?
Another perhaps reasonable definition would provide the digits. Then for example for digit sum we could stop writing sum(map(int, str(n))) and just do sum(n).
If we don’t want to break almost all existing “flatten nested structure” functions, we could only add something like int.as_tuple(), or in the case of the previous example int.as_digits().
I consider iterating over single character as a feature and missing iterating over scalar (int, float, complex, bool, NoneType – all built-in scalar types) could be added (returning scalar).
Still open question is: What can happen/break by adding this?
Besides potential existing “flatten nested structure” functions.
If you have a single integer, then you can use int. If you have some unknown number of integers, you can use (e.g.) list[int]. [5] exactly models the concept of ‘I might have more than one number, but as it happens, I only have the one: five’.
Letting single numbers behave like collections muddys the waters, and can easily mask bugs:
from functools import reduce
import operator
def product(xs):
return reduce(operator.mul, xs, 1)
def geometric_mean_calf_age(cow):
n = len(cow.calves)
return product(cow.age) ** (1 / n)
# Oops, I meant
# return product(calf.age for calf in cow.calves) ** (1 / n)
The TypeError becomes a silent wrong answer.
Mathematics habitually conflates scalars with single-element vectors and matrices. That’s not much of a problem, because, as a mathematician, you can always clarify when necessary. (Though few bother.) But to have a language undermine the distinction for you, everywhere, whether you like it or not, is much worse.
Why should I press the users of my function to distinguish in a call between one or more months? Why should I test it inside the function? See original example at the beginning.
Still open question: What could happen when a scalar (int, float, complex, bool, NoneType) will be iterable returning itself?
allows the user to call f() or f(“jan”) or f(1,2,3). And the neat thing is that months is here always a tuple.
In Python characters are strings that you can iterate over. In Matlab, integers are also arrays that you can iterate over. It causes problems.
It is good for your code to crash early if you’re doing something wrong. If you think something is a tuple, but it is actually an int, there’s a flaw in your mental model of your code, and a TypeError is nice way to find out about that quickly.
If you’ve never had a bug propagate because you’re able to iterate over characters, good for you. But if you write enough code it’s eventually going to happen. When it does, you’ll understand why we don’t want the same potential to exist for scalars and None.
Your question of “what could happen” has already been answered, but I’ll restate it more explicitly:
def foo(x: Iterable[int]) -> None:
...
In the current situation, if someone accidentally calls this with argument 5, when they meant range(5), static analysis can tell them that it’s a problem without even running the code.
With the change you’re suggesting, static analysis doesn’t tell them it’s a problem, and what could happen is hours of frustrated debugging, rather than something that could have been found easily as soon as it was typed.