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:
Many times I want a function parameter that is an iterable but not a string. from collections.abc import Iterable
if isinstance(var, Iterable) and not isinstance(var, str): # put yuour code here
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:
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.
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:
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
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?)
participants (3)
-
Chris Angelico
-
Marco Sulla
-
Steven D'Aprano