Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

What is the pythonic way of "iterating" over a single item?

I come across this issue often, and I would be surprised if there wasn’t some very simple and pythonic one-liner solution to it.

Suppose I have a method or a function that takes a list or some other iterable object as an argument. I want for an operation to be performed once for each item in the object.

Sometimes, only a single item (say, a float value) is passed to this function. In this situation, my for-loop doesn’t know what to do. And so, I find myself peppering my code with the following snippet of code:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

from collections.abc import Sequence

def my_function(value):
   if not isinstance(value, Sequence):
      value = [value]

   # rest of my function

This works, but it seems wasteful and not particularly legible. In searching StackOverflow I’ve also discovered that strings are considered sequences, and so this code could easily break given the wrong argument. It just doesn’t feel like the right approach.

I come from a MATLAB background, and this is neatly solved in that language since scalars are treated like 1×1 matrices. I’d expect, at the very least, for there to be a built-in, something like numpy’s atleast_1d function, that automatically converts anything into an iterable if it isn’t one.

>Solution :

The short answer is nope, there is no simple built-in. And yep, if you want str (or bytes or bytes-like stuff or whatever) to act as a scalar value, it gets uglier. Python expects callers to adhere to the interface contract; if you say you accept sequences, say so, and it’s on the caller to wrap any individual arguments.

If you must do this, there’s two obvious ways to do it:

First is to make your function accept varargs instead of a single argument, and leave it up to the caller to unpack any sequences, so you can always iterate the varargs received:

def my_function(*values):
    for val in values:
        # Rest of function

A caller with individual items calls you with my_function(a, b), a caller with a sequence calls you with my_function(*seq). The latter does incur some overhead to unpack the sequence to a new tuple to be received by my_function, but in many cases this is fine.

If that’s not acceptable for whatever reason, the other solution is to roll your own "ensure iterable" converter function, following whatever rules you care about:

from collections.abc import ByteString

def ensure_iterable(obj):
    if isinstance(obj, (str, ByteString)):
        return (obj,)  # Treat strings and bytes-like stuff as scalars and wrap
    try:
        iter(obj)  # Simplest way to test if something is iterable is to try to make it an iterator
    except TypeError:
        return (obj,)  # Not iterable, wrap
    else:
        return obj  # Already iterable

which my_function can use with:

def my_function(value):
   value = ensure_iterable(value)
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading