Syntactic sugar for checking if a variable is an iterable but not a string?

Many times I want a function parameter that is an iterable but not a string. Usually I do: try: var.__iter__ except AttributeError: # not an iterable else: try: var.isascii except AttributeError: # put yuour code here or from collections.abc import Iterable if isinstance(var, Iterable) and not isinstance(var, str): # put yuour code here The first example uses duck typing but it's more verbose. I use the first method in an home-made utility function. I think it could be interesting to add a syntactic sugar to do this. Maybe a collections.notTextIterable() collections.nonTextIterable() collections.notStrIterable() collections.iterableNotStr() ?

On Fri, Aug 14, 2020 at 6:46 PM Marco Sulla <Marco.Sulla.Python@gmail.com> wrote:
Well, that line of code asks if something is an iterable and not a string. It perfectly captures the intent described in your opening statement. I'd say go with it. IMO the collections module isn't really right for something like that, but if you really need a helper function for this one-liner, then I'd say having one in your own code is perfectly reasonable. ChrisA

On Fri, 14 Aug 2020 at 11:10, Chris Angelico <rosuav@gmail.com> wrote:
In my experience I used it a lot, and I saw many devs online that implemented their solution or asked about it. Not sure if collections module is appropriate, but IMO an helper function will be very useful. @Steven: I continue the discussion with your objections in private.

On Fri, Aug 14, 2020 at 10:43:31AM +0200, Marco Sulla wrote:
That's buggy. Your test for `var.__iter__` has false positives and false negatives: False positives: if an *instance* (but not the class) happens to have an attribute called `__iter__` your test will think it is iterable when it is not. py> class Demo: ... pass ... py> obj = Demo() py> obj.__iter__ = lambda: iter('abc') py> iter(obj) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'Demo' object is not iterable Remember that dunder methods are only called on the class, not on the instance. False negatives: if your class is indexable and obeys the sequence iteration protocol, then it is iterable, but your test will think it is not: py> class Demo: ... def __getitem__(self, i): ... if i > 5: raise IndexError ... return i**2 ... py> list(Demo()) [0, 1, 4, 9, 16, 25] The best and most reliable way to check if something is iterable is to ask iter: try: iter(obj) except TypeError: # not iterable Alas, the Iterable abc does not recognise the sequence iteration protocol, so `isinstance(obj, Iterable)` fails too. Your test for stringness is also buggy: you have no guarantee that an object with an "isascii" attribute is a string. It will give a false positive to any object with an attribute of that name, even if it isn't a method. Why not just test for `isinstance(obj, str)`? (Does anyone care about UserStr?) -- Steven

On Fri, Aug 14, 2020 at 6:46 PM Marco Sulla <Marco.Sulla.Python@gmail.com> wrote:
Well, that line of code asks if something is an iterable and not a string. It perfectly captures the intent described in your opening statement. I'd say go with it. IMO the collections module isn't really right for something like that, but if you really need a helper function for this one-liner, then I'd say having one in your own code is perfectly reasonable. ChrisA

On Fri, 14 Aug 2020 at 11:10, Chris Angelico <rosuav@gmail.com> wrote:
In my experience I used it a lot, and I saw many devs online that implemented their solution or asked about it. Not sure if collections module is appropriate, but IMO an helper function will be very useful. @Steven: I continue the discussion with your objections in private.

On Fri, Aug 14, 2020 at 10:43:31AM +0200, Marco Sulla wrote:
That's buggy. Your test for `var.__iter__` has false positives and false negatives: False positives: if an *instance* (but not the class) happens to have an attribute called `__iter__` your test will think it is iterable when it is not. py> class Demo: ... pass ... py> obj = Demo() py> obj.__iter__ = lambda: iter('abc') py> iter(obj) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'Demo' object is not iterable Remember that dunder methods are only called on the class, not on the instance. False negatives: if your class is indexable and obeys the sequence iteration protocol, then it is iterable, but your test will think it is not: py> class Demo: ... def __getitem__(self, i): ... if i > 5: raise IndexError ... return i**2 ... py> list(Demo()) [0, 1, 4, 9, 16, 25] The best and most reliable way to check if something is iterable is to ask iter: try: iter(obj) except TypeError: # not iterable Alas, the Iterable abc does not recognise the sequence iteration protocol, so `isinstance(obj, Iterable)` fails too. Your test for stringness is also buggy: you have no guarantee that an object with an "isascii" attribute is a string. It will give a false positive to any object with an attribute of that name, even if it isn't a method. Why not just test for `isinstance(obj, str)`? (Does anyone care about UserStr?) -- Steven
participants (3)
-
Chris Angelico
-
Marco Sulla
-
Steven D'Aprano