For some reason, I seem recently to have needed to code a number of functions which take a list of strings as an argument:
setText(aWindow, ['a line', 'another line'])
You often need to pass only one string in, though, and I coded my functions such that you could do this in a 'natural' way:
setText(aWindow, 'just one line this time')
I coded this as follows:
def setText(window, text, append=False):
# Ensure that text is a list
try:
text + ''
text = [text]
except TypeError:
pass
# Rest of funtion, which can assume that 'text' is a list of strings
Robin Munn suggested an improvement to this:
try:
text + ''
except TypeError:
pass
else:
test = [text]
Nice. In my version, I'd have a problem in the (unlikely, but not impossible) case of test = [text] throwing a TypeError.
It's not a huge improvement, true, but I like it. A day in which I learn to improve is a day not wasted.
Posted to Python by Simon Brunning at May 11, 2004 01:01 PMGood question, Greg.
I avoid type() because it's too constricting. Python is a dynamic language, and I don't want to start introducing Java-like static type restrictions.
In this example, 'text' might be a tuple (or anything else following the sequence protocol), and Robin's code (and my code) would work:
setText(aWindow, ('a line', 'another line')) # This is fine
Posted by: Simon Brunning on May 11, 2004 01:23 PMI think you want to adapt the parameter to something that you can iterate over (like a list or tuple).
Here is a "seq" function that returns an iterable from any parameter I would hope. So you can just say text=seq(text) in your code or else: for it in seq(text)
def seq(item, forceit=False):
if not(forceit) and hasattr(item, "__iter__"):
#print "item is iterable"
return item
else:
#print "item is not iterable"
return [item]
def doit(item, forceit=False):
for x in seq(item, forceit):
print "item: "+str(x)
#or you could store it:
# itemlist = seq(item)
# for x in itemlist
def test():
x = "a string"
doit(x)
x = 3
doit(x)
x = [3,4,5]
doit(x)
x = ["one string", "two string", "three string"]
doit(x)
doit(x,True) #force it to create a list containing a list
x = ("one","two","three")
doit(x)
if __name__ == '__main__':
test()
The obvious solution, to me, is:
def setText(window, *args, **kwargs):
....text = ''.join(args)
....append = kwargs.get('append', False)
If you want to pass a sequence, you *expand it in the call. The reason you can't also specify append naturally is because there is no syntax that lets you say that a particular named keyword argument is not allowed to be passed as part of the argument sequence (or vice versa, for positional arguments).
Posted by: Bob Ippolito on May 11, 2004 07:56 PMWhat about:
if isinstance(text, basestring): text = [text]
if all your expecting is strings and list like objects of strings. Its much less likely that someone will reimplement str without deriving from basestring than list.
Posted by: Matthew Sherborne on May 11, 2004 10:46 PMs/your/you're
Posted by: Matthew Sherborne on May 11, 2004 10:48 PMIn fact, what if someone makes a list like class that allows you to append strings (a string list) class if you will, and passes that to the func? Hmmm? :)
I have seen a string list class that does that (it also does auto sorting and stuff) like this:
class StrList(list):
def __add__(self, other): return self[:] + StrList([other])
def __iadd__(self, other):
self.append(other)
return self
>>> l = StrList('one')
>>> l += 'two'
>>> l + 'three'
['one', 'two', 'three']
-----
I have never seen a string like class that didn't derive from basestr.
x = StrList()
x += 'one'
x += 'two'
setText(win, x)
With the try except, you would end up with [['one', 'two', 'three']].
With isinstance() you would end up with: ['one', 'two', 'three']
I think the isinstance is cleaner code and more suited to the situation. It's dificult with dynamic typing to come up with a good and compact type checker.
Perhaps the best way should be to check the first item in the list and see if that's a string, but unfortunately both and list and str will give the same result. :(
I think I'll stick with isinstance(basestr, text) :)
Posted by: Matthew Sherborne on May 11, 2004 11:01 PMOMG, greg, I want to kill you for suggesting usage of <>.
There should be a law!
Here's my solution to a more specific problem, designed to expand a list of filenames:
if type(fpat)<>types.ListType:
fpat=[fpat]
t1=reduce(operator.concat,map(string.split,fpat))
t2=reduce(operator.concat,map(glob.glob,t1))
The first two lines turn a string into a list, and could be replaced by any of the above solutions. The second splits every component of the list by whitespace into a list of individual file specifiers. The last line uses 'glob' to expand all wildcards in all filespecifiers, and leave a single flat list of files.
I've got a suite of handler functions that take and return lists of files, and this code can handle 'human friendly' input like "ab*.fits reduced/b23a.fits", as well as a list of strings (like the output from another handler function).
Posted by: Andrew Williams on May 12, 2004 05:06 AMmoo, I think <> is much more readable (and more widely used) than !=. You can read its component symbols to mean "less-than or greater-than" i.e. not equal.
Posted by: Greg on May 12, 2004 09:49 AMGreg: according to http://docs.python.org/lib/comparisons.html "<>" is obsolescent (I presume they mean obscelete), and "!=" is the officially preferred spelling.
Bob Ippolito: I tend in those situations to name the *args like:
def setText(window, *lines)
...and forget about the kwargs. That way you don't obfuscate the header as much.
Posted by: Robert Brewer on May 12, 2004 04:58 PM(I know, this is somewhat off topic) I like != over <> because != requires only that your values can be compared for equality; <> conceptually requires that your values are totally ordered. (A partial order isn't sufficient, I think.) Requiring ordering is more restrictive than requiring equality tests.
More on topic, the checking for value vs. list thing has bitten me quite a bit (I hate that the % operator on strings does this too), so now I try to avoid writing function interfaces that do such magic. Either I require passing in a list or I use the *args mechanism.
Why not isinstance(type, str)?
Posted by: Baczek on May 13, 2004 08:58 AMit good idea,thank
# Ensure that text is a list
try:
text + ''
text = [text]
except TypeError: