views:

1189

answers:

7

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?

+9  A: 
isinstance(your_var, basestring)
Ayman Hourieh
+15  A: 

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.

David Berger
Surely the more "explicit" method would be to require the user to pass the single-file in a list? Like func(['file1'])
dbr
I agree that that is explicit, but I don't see how it is more explicit. Both techniques use the same logic; one is the reverse of the other. I happen to think that the above is slightly more intuitive, because it emphasizes that the files in the list rather than the list itself is relevant to func.
David Berger
+14  A: 

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 )
Dave
"Personally, I don't really like this sort of behavior", what are you referring to, specifically?
James McMahon
@nemo I think he meant the original question, which could be interpreted as asking for a function whose parameter is either a string or list of strings wherein the function call offers no mention of which type of parameter is passed in. I agree with this.
David Berger
+8  A: 

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

dbr
+2  A: 

if hasattr(f, 'lower'): print "I'm string like"

limscoder
+1  A: 

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.

James McMahon
A: 
def func(files):
    for f in files if not isinstance(files, types.StringTypes) else [files]:
        doSomethingWithFile(f)

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

func('file1')
J.F. Sebastian
POP 8 seems to prefer isinstance(u'abc', basestring) instead of using the types module.
mdorseif