May 11, 2004
Just a Little Better Every Day

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 PM
Comments

Why not use:

if type(text)<>list: text=[text]

Posted by: Greg on May 11, 2004 01:15 PM

Good 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 PM

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

Posted by: DougHolton on May 11, 2004 03:09 PM

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 PM

What 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 PM

s/your/you're

Posted by: Matthew Sherborne on May 11, 2004 10:48 PM

In 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 PM

OMG, greg, I want to kill you for suggesting usage of <>.

There should be a law!

Posted by: moo on May 12, 2004 04:35 AM

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 AM

moo, 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 AM

Greg: according to http://docs.python.org/lib/comparisons.html "<>" is obsolescent (I presume they mean obscelete), and "!=" is the officially preferred spelling.

Posted by: Michael Chermside on May 12, 2004 03:37 PM

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.

Posted by: Amit Patel on May 12, 2004 06:31 PM

Why not isinstance(type, str)?

Posted by: Baczek on May 13, 2004 08:58 AM

it good idea,thank

# Ensure that text is a list
try:
text + ''
text = [text]
except TypeError:

Posted by: andry on July 18, 2004 06:18 PM
Post a comment
Name:


Email Address:


URL:



Comments:


Remember info?