Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

How can I test if an argument is a sequence or a scalar?

0 views
Skip to first unread message

sandra...@yahoo.com

unread,
Jan 10, 2006, 6:18:22 PM1/10/06
to
I want to be able to pass a sequence (tuple, or list) of objects to a
function, or only one.

It's easy enough to do:

isinstance(var, (tuple, list))

But I would also like to accept generators. How can I do this?

Anything else is assumed to be a single value (my fault if I pass a
dict or something else to the function) and I make it into a tuple with
a single value (var, ) in order to simplify the algorithm (wasn't there
a special name for a one item tuple? Its been a while since I've
programmed in Python.)

Is there an easy way to do that?

Thanks,
-Sandra

Jean-Paul Calderone

unread,
Jan 10, 2006, 6:56:26 PM1/10/06
to pytho...@python.org
On 10 Jan 2006 15:18:22 -0800, sandra...@yahoo.com wrote:
>I want to be able to pass a sequence (tuple, or list) of objects to a
>function, or only one.

Generally it's better to keep your API consistent. If you are going
to take sequences, only take sequences. Don't try to make the
interface more convenient by allowing single values to be passed in
by themselves: it just leads to confusion and complexity.

>
>It's easy enough to do:
>
>isinstance(var, (tuple, list))

Above you said sequences - what about strings (buffers, unicode and
otherwise) or arrays or xrange objects? Those are all sequences too,
you know.

>
>But I would also like to accept generators. How can I do this?

Ah, but generators *aren't* sequences. I'm guessing you really want
to accept any "iterable", rather than any sequence. The difference is
that all you can do with an iterable is loop over it. Sequences are
iterable, but you can also index them.

I hope you heed my advice above, but in case you're curious, the easiest
way to tell an iterable from a non-iterable is by trying to iterate over
it. Actually, by doing what iterating over it /would/ have done:

>>> iter("hello world")
<iterator object at 0xb7cae5cc>
>>> iter([1, 2, 3, 4])
<listiterator object at 0xb7cae72c>
>>> def foo():
... yield 1
... yield 2
... yield 3
...
>>> iter(foo())
<generator object at 0xb7cae5cc>

This works for anything that is iterable, as you can see. However, it
won't work for anything:

>>> iter(5)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: iteration over non-sequence

This is "duck typing" in action.

Hope this helps,

Jean-Paul

sandra...@yahoo.com

unread,
Jan 10, 2006, 7:34:12 PM1/10/06
to
Hi Jean-Paul,

Thank you for the advice, I appreciate it, my university training was
next to useless so I learn how to write good programs by the advice of
helpful people in online communities. I'm really thankfull when people
take the time to tell me when I'm doing something wrong and show me a
better way.

And I was still curious, so thank you for explaining how to distinguish
iterables from non-iterables.

Have a great day!

-Sandra

sandra...@yahoo.com

unread,
Jan 10, 2006, 7:34:18 PM1/10/06
to

Michael Spencer

unread,
Jan 10, 2006, 8:59:56 PM1/10/06
to pytho...@python.org
Jean-Paul Calderone wrote:
> On 10 Jan 2006 15:18:22 -0800, sandra...@yahoo.com wrote:
>> I want to be able to pass a sequence (tuple, or list) of objects to a
>> function, or only one.
>
...

> but in case you're curious, the easiest
> way to tell an iterable from a non-iterable is by trying to iterate over
> it. Actually, by doing what iterating over it /would/ have done:
>
...

You can usefully wrap up this test in a convenience function that always returns
an iterator (possibly over a zero length sequence):

>>> for i in safe_iter(scalar_or_iterable): #do something

One definition of safe_iter is below. Your definition may depend on the
specific application:

Cheers

Michael


def safe_iter(obj, atomic_types = (basestring, int, float, complex)):
"""Equivalent to iter when obj is iterable and not defined as atomic.
If obj is defined atomic or found to be not iterable, returns iter((obj,)).
safe_iter(None) returns an empty iterator"""
if not isinstance(obj, atomic_types):
try:
return iter(obj)
except TypeError:
pass
return iter((obj,) * (obj is not None))

def test_safe_iter():
assert list(safe_iter(1)) == [1]
assert list(safe_iter("string")) == ["string"]
assert list(safe_iter(range(10))) == range(10)
assert list(safe_iter(xrange(10))) == list(xrange(10))
assert list(safe_iter((1,2,3))) == [1,2,3]
assert list(safe_iter(1.0)) == [1.0]
assert list(safe_iter(1+2j)) == [1+2j]
xiter = iter(range(10))
assert safe_iter(xiter) is xiter
xiter = (a for a in range(10))
assert safe_iter(xiter) is xiter
assert list(safe_iter(None)) == []


Fuzzyman

unread,
Jan 11, 2006, 8:00:11 AM1/11/06
to
That's a neat little piece of code there.

All the best,

Fuzzyman
http://www.voidspace.org.uk/python/index.shtml

Russell E. Owen

unread,
Jan 11, 2006, 2:07:14 PM1/11/06
to
In article <1136935102....@o13g2000cwo.googlegroups.com>,
sandra...@yahoo.com wrote:

Here is the code I use for that purpose, but it does not handle
generators or iterators. You could easily combine this with code
suggested in other replies to accomplish that. (I intentionally excluded
iterators because I didn't need them and they can be unbounded. Still,
now that they are so widely used it may be worth reconsidering.)

def isSequence(item):
"""Return True if the input is a non-string sequential collection,
False otherwise. Note: dicts and sets are not sequential.
"""
try:
item[0:0]
except (AttributeError, TypeError):
return False
return not isString(item)

def isString(item):
"""Return True if the input is a string-like sequence.
Strings include str, unicode and UserString objects.

From Python Cookbook, 2nd ed.
"""
return isinstance(item, (basestring, UserString.UserString))

def asSequence(item):
"""Converts one or more items to a sequence,
If item is already a non-string-like sequence, returns it unchanged,
else returns [item].
"""
if isSequence(item):
return item
else:
return [item]

I then use asSequence on each argument that can either be one item or a
sequence of items (so I can always work on a sequence).

-- Russell

P.S. this code is from RO.SeqUtil
<http://www.astro.washington.edu/rowen/ROPython.html>

0 new messages