Iterable scalar values returning itself ones?
Are there any reasons not to make scalar types iterable returning the value ones? Should each function check if it has got one value or a list/tuple before iteration over the argument? What is wrong on scalars for iteration, please? There is even legal to iterate over an empty set – an empty cycle. Python 3.8.5 (default, Jan 27 2021, 15:41:15)
def chain(*iterables): ... for iterable in iterables: ... yield from iterable ... a = 5 b = range(3) for i in chain(a, b): ... print(i) ... Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 3, in chain TypeError: 'int' object is not iterable
HG
Whenever you extend the definition of an operation (`__iter__` in this case) to more existing objects, you lose a little bit of the ability to catch errors early. Consider the function: def traverse(something): for x in something: # do stuff ... If you accidentally call `traverse(42)`, then right now, you catch it immediately with a TypeError on the `for x in something:` line. Under your proposal, you might just get a strange answer and not realize anything is wrong. Besides, we already have ways to write "repeat 1 ten times", using `itertools.repeat(1, times=10)`. Or if you just need any iterable with ten items, you can use range(10). I think keeping numbers non-iterable is nice based on a couple of the lines PEP 20: `Explicit is better than implicit`, and `There should be one-- and preferably only one --obvious way to do it.`
On Tue, Apr 13, 2021 at 05:39:42PM -0000, Dennis Sweeney wrote:
Whenever you extend the definition of an operation (`__iter__` in this case) to more existing objects, you lose a little bit of the ability to catch errors early. Consider the function:
def traverse(something): for x in something: # do stuff ...
If you accidentally call `traverse(42)`, then right now, you catch it immediately with a TypeError on the `for x in something:` line. Under your proposal, you might just get a strange answer and not realize anything is wrong.
Error? Perhaps feature! You will get the desired behaviour and don't need to handle corner case like this. from collections.abc import Iterable def traverse(s): for x in s if isinstance(s, Iterable) else (s,): print(f"{x=}") traverse(5) traverse(range(3)) H.
It wasn't clear to me what you are proposing: iter(an_int) to return. There have been multiple proposal in the past for it to return the same thing as: iter(range(an_int)), so that one could write: for i in 23: ... more compactly than: for i in range(23): ... but that proposal has been rejected many times. On the other hand, maybe you are proposing that: iter(5) Should return the same thing as: iter((5,)) which is really odd. It makes some small amount of sense if you assume that all sequences are "flat". But even then, the distinction between a single item and a sequence with one item in it a critical distinction that should not be masked. Also - if you do this for integers, do you do it for all numbers? what about any other single object? (and THAT would get really strange with strings!) BTW: I'm using `iter` here because that is the iteration protocol -- anything we do needs to conform to that. - CHB On Wed, Apr 14, 2021 at 1:35 AM Hans Ginzel <hans@matfyz.cz> wrote:
Whenever you extend the definition of an operation (`__iter__` in this case) to more existing objects, you lose a little bit of the ability to catch errors early. Consider the function:
def traverse(something): for x in something: # do stuff ...
If you accidentally call `traverse(42)`, then right now, you catch it immediately with a TypeError on the `for x in something:` line. Under your
On Tue, Apr 13, 2021 at 05:39:42PM -0000, Dennis Sweeney wrote: proposal, you might just get a strange answer and not realize anything is wrong.
Error? Perhaps feature! You will get the desired behaviour and don't need to handle corner case like this.
from collections.abc import Iterable def traverse(s): for x in s if isinstance(s, Iterable) else (s,): print(f"{x=}")
traverse(5) traverse(range(3))
H. _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/UIJRGC... Code of Conduct: http://python.org/psf/codeofconduct/
-- Christopher Barker, PhD (Chris) Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython
On Wed, Apr 14, 2021 at 09:05:17AM -0700, Christopher Barker wrote:
so that one could write: for i in 23: ...
I am proposing this ill run the cycle ones with i=23.
iter(5) Should return the same thing as: iter((5,))
Yes. As I wrote in the "traverse" example below iter(s) should return the same thing as: from collections.abc import Iterable iter(s if isinstance(s, Iterable) else (s,))
which is really odd. It makes some small amount of sense if you assume that all sequences are "flat". But even then, the distinction between a single item and a sequence with one item in it a critical distinction that should not be masked.
Why, please? Can you give more (concrete) examples, please?
Also - if you do this for integers, do you do it for all numbers? what about any other single object?
As I wrote for any (not iterable) scalar/single value/object.
(and THAT would get really strange with strings!)
Strings are lists by definition. Yes, the behaviour for strings would be surprising. But why to discriminate all other types, because strings are lists?
Error? Perhaps feature! You will get the desired behaviour and don't need to handle corner case like this.
from collections.abc import Iterable def traverse(s): for x in s if isinstance(s, Iterable) else (s,): print(f"{x=}")
traverse(5) traverse(range(3))
H.
On Fri, Apr 16, 2021 at 7:32 PM Hans Ginzel <hans@matfyz.cz> wrote:
On Wed, Apr 14, 2021 at 09:05:17AM -0700, Christopher Barker wrote:
so that one could write: for i in 23: ...
I am proposing this ill run the cycle ones with i=23.
And other people would expect it to run 23 times. Integers are not iterable and it's far better to keep them that way. If you need to iterate once over an integer, just surround it in square brackets.
a = [5] b = range(3) for i in chain(a, b): ... print(i)
Job done, no need to try to pretend that everything is a set containing itself. ChrisA
On Tue, 2021-04-13 at 17:00 +0200, Hans Ginzel wrote:
Are there any reasons not to make scalar types iterable returning the value ones? Should each function check if it has got one value or a list/tuple before iteration over the argument? What is wrong on scalars for iteration, please? There is even legal to iterate over an empty set – an empty cycle.
Iterating an empty set is well defined. I can ask you to "move all cars from this parking lot and park them in a row so that I can clean them one by one". It works just as well if there is no car to move. There will probably be enough reasons why just adding it can break code or how it would be confusing to do it only for numbers but not for other python objects (assuming you limit this to numbers, since I assume making all objects iterable like this would come with its own problems). Conceptually, my main argument is that iteration in Python (the builtins) is typically "1-D". That is, instead of an N-dimensional container, a Python user will often use a nested list-of-lists. And then iteration goes only over the first list. If you iterate 0-D as "all elements", you should do so also for N-D in my opinion (i.e. iterate all elements in a 2-D matrix). This choice is inherited (for better or worse) by NumPy. In principle, if you designed everything around N-D objects, iterating all elements by default seems like a great choice (I would probably prefer that for NumPy, if I had the choice to go back in time). In that case the "scalar" can be seen as a 0-D object and allowing to iterate it is reasonable. (Even then there are questions about mutability/being a container, so it is not obvious to me, but we would get very close!) That could (and likely should) be how projects like NumPy do things. But, it doesn't strike me as a good fit for the rest of the Python builtins which mainly provides 1-D `sequences` without much of a concept of N-D containers. Cheers, Sebastian
Python 3.8.5 (default, Jan 27 2021, 15:41:15)
def chain(*iterables): ... for iterable in iterables: ... yield from iterable ... a = 5 b = range(3) for i in chain(a, b): ... print(i) ... Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 3, in chain TypeError: 'int' object is not iterable
HG _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/UTVIGX... Code of Conduct: http://python.org/psf/codeofconduct/
Hans Ginzel writes:
Are there any reasons not to make scalar types iterable returning the value ones?
Yes. Scalars aren't containers. Except strs, which are containers all the way down. :-þ If we must iterate scalars, then at the very least we should conform to existing mathematical interpretations of scalars as containers. For example for i in 3: print(i) could produce set() {set()} {set(), {set()}} (or perhaps an arbitrary shuffle of those, since 3 is a scalar, there's no order to be preserved!) and for i in 16853193807528738685: print(i) could produce "h" "e" "l" "l" "o" " " "g" "o" "e" "d" "e" "l" More seriously, the string non-example gives a hint at the real reason. Some algorithms iterate and recurse over containers of containers of ... until they hit scalars, then they do something with the scalars. Such algorithms will infloop if scalars are made iterable. This comes up with respect to strings occasionally (even on the dev lists where everybody knows nothing is going to be done about one-character strings). That kind of thing means that you're going to have to check for types you don't want to iterate in many cases. And it will drive type-checking and linting software mad. Steve
On Fri, Apr 16, 2021 at 1:35 AM David Mertz <mertz@gnosis.cx> wrote:
Yes. Scalars aren't containers. Except strs, which are containers
all the way down. :-þ
Wow... how do I get a keyboard with a thorn character on it? :-)
Configure a Compose key, then use this: <Multi_key> <t> <h> : "þ" thorn # LATIN SMALL LETTER THORN Incidentally, the same file has this in it: <Multi_key> <braceleft> <braceright> : "∅" U2205 # EMPTY SET So brace-left, brace-right is "{}" but compose, brace-left, brace-right is "∅". If absolutely every Python programmer had this available, it might be enough to distinguish empty dict from empty set..... ChrisA
On 2021-04-16 at 04:03:33 +1000, Chris Angelico <rosuav@gmail.com> wrote:
So brace-left, brace-right is "{}" but compose, brace-left, brace-right is "∅". If absolutely every Python programmer had this available, it might be enough to distinguish empty dict from empty set.....
But if an empty set is composed from an empty dict, then the empty set is not empty. Unless it weighs as much as a duck? Or your're Dutch?
participants (8)
-
2QdxY4RzWzUUiLuE@potatochowder.com
-
Chris Angelico
-
Christopher Barker
-
David Mertz
-
Dennis Sweeney
-
Hans Ginzel
-
Sebastian Berg
-
Stephen J. Turnbull