is parameter an iterable?

P

py

I have function which takes an argument. My code needs that argument
to be an iterable (something i can loop over)...so I dont care if its a
list, tuple, etc. So I need a way to make sure that the argument is an
iterable before using it. I know I could do...

def foo(inputVal):
if isinstance(inputVal, (list, tuple)):
for val in inputVal:
# do stuff

....however I want to cover any iterable since i just need to loop over
it.

any suggestions?
 
D

Dan Sommers

I have function which takes an argument. My code needs that argument
to be an iterable (something i can loop over)...so I dont care if its a
list, tuple, etc. So I need a way to make sure that the argument is an
iterable before using it. I know I could do...
def foo(inputVal):
if isinstance(inputVal, (list, tuple)):
for val in inputVal:
# do stuff
...however I want to cover any iterable since i just need to loop over
it.
any suggestions?

Just do it. If one of foo's callers passes in a non-iterable, foo will
raise an exception, and you'll catch it during testing. Watch out for
strings, though:
... for j in i:
... print j
1
3
4
5
6
7 h
e
l
l
o

Regards,
Dan
 
P

py

Dan said:
Just do it. If one of foo's callers passes in a non-iterable, foo will
raise an exception, and you'll catch it during testing

That's exactly what I don't want. I don't want an exception, instead I
want to check to see if it's an iterable....if it is continue, if not
return an error code. I can't catch it during testing since this is
going to be used by other people.

Thanks for the suggestion though.
 
M

marduk

I have function which takes an argument. My code needs that argument
to be an iterable (something i can loop over)...so I dont care if its a
list, tuple, etc. So I need a way to make sure that the argument is an
iterable before using it. I know I could do...

def foo(inputVal):
if isinstance(inputVal, (list, tuple)):
for val in inputVal:
# do stuff

...however I want to cover any iterable since i just need to loop over
it.

any suggestions?

You could probably get away with

if hasattr(inputVal, '__getitem__')
 
R

Rocco Moretti

marduk said:
You could probably get away with

if hasattr(inputVal, '__getitem__')

No, you probably couldn't.

################## for i in xrange(s):
yield i+s

False
###################

I'd do something like:

#####################
def foo(inputVal):
try:
iter(inputVal) # Can you change it into an interator?
except TypeError:
# Return Error Code
else:
for val in inputVal:
# do stuff
#######################

Again, you'll have to be careful about strings.
 
R

Robert Kern

py said:
That's exactly what I don't want. I don't want an exception, instead I
want to check to see if it's an iterable....if it is continue, if not
return an error code.

Why return an error code? Just pass along the exception (i.e. do nothing
special). Python's exception mechanism is far superior to error codes.
Don't try to fight the language.
I can't catch it during testing since this is
going to be used by other people.

Then *they'll* catch it during testing.

--
Robert Kern
(e-mail address removed)

"In the fields of hell where the grass grows high
Are the graves of dreams allowed to die."
-- Richard Harter
 
C

Carl Friedrich Bolz

Hi!
That's exactly what I don't want. I don't want an exception, instead I
want to check to see if it's an iterable....if it is continue, if not
return an error code. I can't catch it during testing since this is
going to be used by other people.

Note that using error codes is usually quite "unpythonic", the way to
signal that something is exceptional (not necessarily wrong) is, well,
an exception.

Anyway, one way to solve this is the following:

def foo(input_val):
try:
iterator = iter(input_val)
except TypeError:
# do non-iterable stuff
else:
for val in iterator:
# do loop stuff

Cheers,

Carl Friedrich Bolz
 
P

py

Thanks for the replies. I agree with Jean-Paul Calderone's
suggestion...let the exception be raised.

Thanks.
 
R

Roy Smith

I have function which takes an argument. My code needs that argument
to be an iterable (something i can loop over)...so I dont care if its a
list, tuple, etc.

My first thought was to just write your loop inside a try block and
catch the error if it wasn't iterable, but then I noticed that you get:

TypeError: iteration over non-sequence

I was kind of hoping for a more specific exception than TypeError.
You can't tell the difference between:

try:
for i in 5:
print i + 1
except TypeError:
print "non-iterable"

and

try:
for i in ["one", "two", "three"]:
print i + 1
except TypeError:
print "can't add string and integer"

Unfortunately, you can't just try it in a bodyless loop to prove that
you can iterate before doing the real thing because not all iterators
are idempotent.

It's an interesting problem.
 
G

Grant Edwards

That's exactly what I don't want. I don't want an exception, instead I
want to check to see if it's an iterable....if it is continue, if not
return an error code. I can't catch it during testing since this is
going to be used by other people.

If I were those other people, and you decided to return error
codes to me instead of passing up the proper exception (the
good, Pythonic thing to do), I'd be fairly pissed off at you.

An exception is the _right_ way to let the caller know
something is wrong.
 
D

Dave Hansen

That's exactly what I don't want. I don't want an exception, instead I
want to check to see if it's an iterable....if it is continue, if not
return an error code. I can't catch it during testing since this is
going to be used by other people.

Then catch the exception yourself.
try:
for j in i:
print j
print "Success!"
return 0
except TypeError, e:
print "Bad foo. No donut.", e
return -1

1
3
5
7
9
Success!
Regards,
-=Dave
 
S

Steven D'Aprano

....

Just do it. If one of foo's callers passes in a non-iterable, foo will
raise an exception, and you'll catch it during testing.

It isn't during testing so much that he needs to watch out, as run-time.
You have three options:

(1) The "if the user is silly enough to pass a non-iterable to my
function, they deserve to have it fail" school of thought. Dan's advise
comes under this heading.

(2) The "if the user passes a non-iterable, I want to catch the exception
and recover gracefully" school of thought. Recovering gracefully may mean
raising your own exception, with a user friendly error message ("Hey
butthead, pass an iterable willya!!!"), or it may mean just catching the
exception and doing something else, depending on the needs of your
program. (For example, as a user-friendly feature, you might want to treat
ints as if they were iterables of one item only, so users can pass 5 as an
argument instead of [5].)

For the second case, what you want to do is wrap your code in a
try...except block and catch the exception raised when a non-iterator is
passed as an argument.

Which exception is that? I leave that as an exercise for the reader.
(Hint: Just Do It and read the traceback Python prints.)

Actually, I think "Just Do It" might make a good motto for Python.
 
B

Ben Finney

Maybe this helps:

inValType = type(inputVal)
if inValType==types.ListType or inValType==types.TupleType:

And what of user-created types that are iterable?

What of user-created iterable types that don't inherit from any of the
built-in iterable types?
 
S

Steven D'Aprano

That's exactly what I don't want. I don't want an exception, instead I
want to check to see if it's an iterable....if it is continue, if not
return an error code. I can't catch it during testing since this is
going to be used by other people.

That would be the Look Before You Leap school of thought.

That is rarely the best way of doing things in Python. (Note I said
*rarely*, not *never*.) If you insist, you would have to do something like
this:

def is_iterable(obj):
"""Returns True if obj is *probably* an iterable and False
otherwise. Not that some weird custom objects that break the
rules may give incorrect results.
"""
if type(obj) in (type([]), type(()), type("")):
return True
elif hasattr(obj, next) and callable(obj.next):
return True
elif hasattr(obj, __getitem__):
return True
else:
# I don't think I missed anything...
return False

But in fact there is an easier way:

def is_iterable(obj):
"""Just Do It."""
try:
for item in obj:
break
except TypeError:
return False
return True

That works, but it has a side-effect: if you pass it an iterable that gets
consumed, you now have used up the first item of it (e.g. if you a
reading lines from a file, you've just used the first line and won't get
it back unless you reset the file pointer).

So do your test inside your code:

def foo(inputVal):
try:
for val in inputVal:
# do stuff
except TypeError, msg:
if msg == "iteration over non-sequence":
# handle non-iterable case
else:
# some other TypeError is a bug, so re-raise the exception
raise
 
A

Alex Martelli

Roy Smith said:
TypeError: iteration over non-sequence

I was kind of hoping for a more specific exception than TypeError.
You can't tell the difference between:

try:
for i in 5:
print i + 1
except TypeError:
print "non-iterable"

and

try:
for i in ["one", "two", "three"]:
print i + 1
except TypeError:
print "can't add string and integer"

Unfortunately, you can't just try it in a bodyless loop to prove that
you can iterate before doing the real thing because not all iterators
are idempotent.

It's not hard...:

try:
_it = iter(whatever)
except TypeError:
print 'non-iterable'
else:
for i in _it: # etc, etc

Alex
 
S

Steven D'Aprano

Roy said:
You can't tell the difference between:

try:
for i in 5:
print i + 1
except TypeError:
print "non-iterable"

and

try:
for i in ["one", "two", "three"]:
print i + 1
except TypeError:
print "can't add string and integer"


try:
for item in obj:
do_stuff(item)
except TypeError, msg:
if msg == "iteration over non-sequence":
handle_non_iterator()
else:
# re-raise the exception
raise
 
S

Steven D'Aprano

Maybe this helps:

Unfortunately, it doesn't. If you read the sample code
the original poster first gave, you will see that Py's
example is virtually the same as yours:

[original code]
def foo(inputVal):
if isinstance(inputVal, (list, tuple)):
for val in inputVal:
# do stuff

It uses the same program logic, only the implementation
is different: your code tests the object's type, and
compares it to the type of list and tuple. Py's code
tests the object's type, comparing it to lists and tuples.

Type testing gives less information than using
isinstance, and takes more work to do it. For instance,
Py's code will correctly work with subclasses of lists,
yours will wrongly reject them.

Check Py's requirements carefully:

"I have function which takes an argument. My code
needs that argument to be an iterable (something i can
loop over)...so I dont care if its a list, tuple, etc."

Notice the "etc"? There are lots of iterables other
than lists and tuples. There are xrange objects, files,
strings, buffers, sub-classes of all of those, classes
that implement __getitem__(), classes that implement
next(), and probably more things that I have forgotten.

Your code doesn't check for any of those.
 
D

Dan Sommers

try:
for item in obj:
do_stuff(item)
except TypeError, msg:
if msg == "iteration over non-sequence":
handle_non_iterator()
else:
# re-raise the exception
raise

But what it do_stuff tries to iterate over a non-sequence?

Regards,
Dan
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,995
Messages
2,570,230
Members
46,819
Latest member
masterdaster

Latest Threads

Top