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:
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.
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 )
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.
if hasattr(f, 'lower'): print "I'm string like"
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.
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!)