How can I tell if a python variable is a string or a list?

Question:

I have a routine that takes a list of strings as a parameter, but I’d like to support passing in a single string and converting it to a list of one string. For example:

def func( files ):
    for f in files:
        doSomethingWithFile( f )

func( ['file1','file2','file3'] )

func( 'file1' ) # should be treated like ['file1']

How can my function tell whether a string or a list has been passed in? I know there is a type function, but is there a “more pythonic” way?

Asked By: Graeme Perrow

||

Answers:

isinstance(your_var, basestring)
Answered By: Ayman Hourieh

Well, there’s nothing unpythonic about checking type. Having said that, if you’re willing to put a small burden on the caller:

def func( *files ):
    for f in files:
         doSomethingWithFile( f )

func( *['file1','file2','file3'] ) #Is treated like func('file1','file2','file3')
func( 'file1' )

I’d argue this is more pythonic in that “explicit is better than implicit”. Here there is at least a recognition on the part of the caller when the input is already in list form.

Answered By: David Berger

Personally, I don’t really like this sort of behavior — it interferes with duck typing. One could argue that it doesn’t obey the “Explicit is better than implicit” mantra. Why not use the varargs syntax:

def func( *files ):
    for f in files:
        doSomethingWithFile( f )

func( 'file1', 'file2', 'file3' )
func( 'file1' )
func( *listOfFiles )
Answered By: Dave

I would say the most Python’y way is to make the user always pass a list, even if there is only one item in it. It makes it really obvious func() can take a list of files

def func(files):
    for cur_file in files:
        blah(cur_file)

func(['file1'])

As Dave suggested, you could use the func(*files) syntax, but I never liked this feature, and it seems more explicit (“explicit is better than implicit”) to simply require a list. It’s also turning your special-case (calling func with a single file) into the default case, because now you have to use extra syntax to call func with a list..

If you do want to make a special-case for an argument being a string, use the isinstance() builtin, and compare to basestring (which both str() and unicode() are derived from) for example:

def func(files):
    if isinstance(files, basestring):
        doSomethingWithASingleFile(files)
    else:
        for f in files:
            doSomethingWithFile(f)

Really, I suggest simply requiring a list, even with only one file (after all, it only requires two extra characters!)

Answered By: dbr
if hasattr(f, 'lower'): print "I'm string like"
Answered By: limscoder

Varargs was confusing for me, so I tested it out in Python to clear it up for myself.

First of all the PEP for varargs is here.

Here is sample program, based on the two answers from Dave and David Berger, followed by the output, just for clarification.

def func( *files ):
    print files
    for f in files:
        print( f )

if __name__ == '__main__':
    func( *['file1','file2','file3'] ) #Is treated like func('file1','file2','file3')
    func( 'onestring' )
    func( 'thing1','thing2','thing3' )
    func( ['stuff1','stuff2','stuff3'] )

And the resulting output;

('file1', 'file2', 'file3')
file1
file2
file3
('onestring',)
onestring
('thing1', 'thing2', 'thing3')
thing1
thing2
thing3
(['stuff1', 'stuff2', 'stuff3'],)
['stuff1', 'stuff2', 'stuff3']

Hope this is helpful to somebody else.

Answered By: James McMahon
def func(files):
    for f in files if not isinstance(files, basestring) else [files]:
        doSomethingWithFile(f)

func(['file1', 'file2', 'file3'])

func('file1')
Answered By: jfs

If you have more control over the caller, then one of the other answers is better. I don’t have that luxury in my case so I settled on the following solution (with caveats):

def islistlike(v):
   """Return True if v is a non-string sequence and is iterable. Note that
   not all objects with getitem() have the iterable attribute"""
   if hasattr(v, '__iter__') and not isinstance(v, basestring):
       return True
   else:
       #This will happen for most atomic types like numbers and strings
       return False

This approach will work for cases where you are dealing with a know set of list-like types that meet the above criteria. Some sequence types will be missed though.

Answered By: Dana the Sane
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.