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?)