Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python, allowing passing single string and list of string to a function

I have a function that makes a call to a REST API. One of the parameter is a comma-separated list of names. To generate it, I do this:

','.join(names)

However, I also want to allow the user to provide a single name. The problem is that if names = ERII, for instance, it results in ['E', 'R', 'I', 'I'], but I need it to be ['ERII'] instead.

Now, I could force the user to enter a list with only one value (names=['ERRI'] or names=('ERII',). I would prefer to allow the user to provide a single String. Is there a clever way to do that without a if else statement checking if the provided value is an Iterable?

Also, I am uncertain as what would be the best practice here, force to provide a list, or allow a single string?

like image 809
Antoine Viscardi Avatar asked Nov 30 '22 08:11

Antoine Viscardi


1 Answers

Parameters that can be either a thing or an iterable or things are a code smell. It’s even worse when the thing is a string, because a string is an iterable, and even a sequence (so your test for isinstance(names, Iterable) would do the wrong thing).

The Python stdlib does have a few such cases—most infamously, str.__mod__—but most of those err in the other direction, explicitly requiring a tuple rather than any iterable, and most of them are considered to be mistakes, or at least things that wouldn’t be added to the language today. Sometimes it is still the best answer, but the smell should make you think before doing it.

I don’t know exactly what your use case is, but I suspect this will be a lot nicer:

def spam(*names):
    namestr = ','.join(names)
    dostuff(namestr)

Now the user can call it like this:

spam('eggs')
spam('eggs', 'cheese', 'beans')

Or, if they happen to have a list, it’s still easy:

spam(*ingredients)

If that’s not appropriate, another option is keywords, maybe even keyword-only params:

def spam(*, name=None, names=None):
    if name and names:
        raise TypeError('Not both!')
    if not names: names = [name]

But if the best design really is a string or a (non-string) iterable of strings, or a string or a tuple of strings, the standard way to do that is type switching. It may look a bit ugly, but it calls attention to the fact that you’re doing exactly what you’re doing, and it does it in the most idiomatic way.

def spam(names):
    if isinstance(names, str):
        names = [names]
    dostuff(names)
like image 52
abarnert Avatar answered Dec 05 '22 02:12

abarnert