4

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)?

2
  • You could also just pass 'John' as a single item list, e.g., ['John'], and define the function expecting the for Commented May 18, 2012 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 Commented May 18, 2012 at 14:43

6 Answers 6

7

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 >:(
Sign up to request clarification or add additional context in comments.

1 Comment

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).
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'])

1 Comment

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.
2

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")

Comments

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()

Comments

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.

Comments

-2

You can check the type of your parameter:

>>> type('John')
<type 'str'>
>>> type(['explicit', 'is', 'better'])
<type 'list'>

1 Comment

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.