3

Let's say I want to add a list of strings or just one to a DB:

names = ['Alice', 'Bob', 'Zoe']

and I want that add_to_db will accept both cases

add_to_db(names)
add_to_db('John')

Is this the correct way to go:

def add_to_db(name_or_names):
    if (...):
        ... # add a single name
    else:
        for name in name_or_names:
            ... # add a single name
    commit_to_db()

What should I put in the first ..., as condition to whether it's a list of strings or just a string (don't forget that string is also iterable)?

zenpoy
  • 19,490
  • 9
  • 60
  • 87
  • You could also just pass 'John' as a single item list, e.g., `['John']`, and define the function expecting the `for` – hexparrot May 18 '12 at 14:40
  • Yeah, but as I already wrote in another comment I have to support `add_to_db('John')` as this is already implemented and I don't want to change all calls to the functions – zenpoy May 18 '12 at 14:43

6 Answers6

6

I think your best bet is to cast a single string to a 1-element list, and then have the rest of your function deal exclusively with lists.

def add_to_db(name_or_names):
    import types
    if isinstance(name_or_names, types.StringTypes):
        name_or_names = [name_or_names]
    try:
        for name in name_or_names:
            add_name(name)
        commit_to_db()
    except TypeError:
            # we didn't get a string OR a list >:(
Kenan Banks
  • 207,056
  • 34
  • 155
  • 173
  • 1
    Good approach, but two things: 1) you can just go `isinstance(name_or_names, basestring)`, as the builtin `basestring` is a supertype of `str` and `unicode`; 2) your example will catch a `TypeError` raised by add_name() or commit_to_db(), which may hide bugs -- one should always try/except around the smallest amount of code possible. In this case, you'd usually just remove the try/except and let the error bubble up to the caller (it's a programming bug). – Ben Hoyt Sep 13 '12 at 01:26
4

Use keyword arguments:

def add_to_db(name=None, names=None):
    if name:
        .....
    elif names:
        ....
    else:
        raise ValueError("Invalid arguments")

add_to_db(name='John')
add_to_db(names=['explicit', 'is', 'better'])
georg
  • 211,518
  • 52
  • 313
  • 390
  • That works as expected also with `add_to_db('John')` since it's the first arg. This is good for me because I'm extending an already existing function and I don't want to change all the old calls. – zenpoy May 18 '12 at 14:36
1

It's possibly easier to test whether the object is a string rather than iterable. This is a technique I've used before. Make sure it's a list, then loop through it:

def add_to_db(name_or_names):
    if isinstance(name_or_names, basestring):
        # str or unicode, convert to list
        name_or_names = [name_or_names]
    for name in name_or_names:
        add_name_to_db()
Ben Hoyt
  • 10,694
  • 5
  • 60
  • 84
1

You can declare the instance type like:

from typing import List, Union

def my_fancy_func(l: Union[List [str], str]):
  if not isinstance(l, list):
    print("Error", type(sentence))
  else:
    print("Everything's okay")
Álvaro H.G
  • 397
  • 1
  • 10
0

You can test if the argument has the __iter__ attribute, for which strings will fail:

if hasattr(arg, '__iter__'):
    print "I am an iterable that is not a string!"

From this answer.

Community
  • 1
  • 1
matt b
  • 138,234
  • 66
  • 282
  • 345
-2

You can check the type of your parameter:

>>> type('John')
<type 'str'>
>>> type(['explicit', 'is', 'better'])
<type 'list'>
uselpa
  • 18,732
  • 2
  • 34
  • 52
  • 4
    Checking for equality of types, as you seem to be suggesting (ie, test `type(arg) == type('')`) is almost always a bad idea. Doing `isinstance(arg, str)` will also match user-defined string subclasses, for example. – lvc May 18 '12 at 14:58