is there a pythonic way to handle arguments that could be containers or strings?

Viewed 75

I run into the following two issues at the same time fairly frequently

  1. I have a function with an argument that's expected to be a container of strings
  2. I would like to simplify calls by passing the function either a container of strings or a single string that is not a singleton list

I usually handle this with something like the following, which seems somehow not pythonic to me (I don't know why). Is there a more pythonic way to handle the situation? Is this actually a bad habit and it's most appropriate to require function calls like my_func(['just_deal_with_it'])?

Note the function iterable_not_string below is from sorin's answer to another question

from collections.abc import Iterable

def iterable_not_string(x):

    is_iter = isinstance(x, Iterable)
    is_not_str = (not isinstance(x, str))
    return (is_iter and is_not_str)

def my_func(list_or_string):

    if iterable_not_string(list_or_string):
        do_stuff(list_or_string)
    else:
        do_stuff([list_or_string])
3 Answers

I use the following idiom, which works with any flexibly typed language:

def my_func(arg):
    """arg can be a list, or a single string"""

    if isinstance(arg, str):
        arg = [ arg ]

    # the rest of the code can just treat `arg` as a list
    do_stuff_with_a_list(arg)

By normalizing the argument to a list at the start, you avoid code duplication and type-checking later... and the attendant bugs if you forget a check.

Another options is to accept arbitrary arguments

def my_func(*strings):
    do_stuff(strings)

my_func('string')
l = ['list', 'of', 'strings']
my_func(*l)

However, I advise to only do this, if the amount of elements is expected to be small, since the unpacking of the iterable may take some time and consume a lot of memory (i.e. on long generators).

You can do this with the python library typing

Typing provids type hinting for python

For example:

from typing import List, Union

def my_func(list_or_string: Union[List, str]):
   ...

Python 3.10 provides a cleaner approach to this:

from typing import List

def my_func(list_or_string: List | str):
  ...

In this case Union is replaced by the | (pipe)

Related