tags:

views:

195

answers:

7

I am using a 3rd party library function which reads a set of keywords from a file, and is supposed to return a tuple of values. It does this correctly as long as there are at least two keywords. However, in the case where there is only one keyword, it returns a raw string, not a tuple of size one. This is particularly pernicious because when I try to do something like

for keyword in library.get_keywords():
    # Do something with keyword

, in the case of the single keyword, the for iterates over each character of the string in succession, which throws no exception, at run-time or otherwise, but is nevertheless completely useless to me.

My question is two-fold:

Clearly this is a bug in the library, which is out of my control. How can I best work around it?

Secondly, in general, if I am writing a function that returns a tuple, what is the best practice for ensuring tuples with one element are correctly generated? For example, if I have

def tuple_maker(values):
    my_tuple = (values)
    return my_tuple

for val in tuple_maker("a string"):
    print "Value was", val

for val in tuple_maker(["str1", "str2", "str3"]):
    print "Value was", val

I get

Value was a
Value was  
Value was s
Value was t
Value was r
Value was i
Value was n
Value was g
Value was str1
Value was str2
Value was str3

What is the best way to modify the function my_tuple to actually return a tuple when there is only a single element? Do I explicitly need to check whether the size is 1, and create the tuple seperately, using the (value,) syntax? This implies that any function that has the possibility of returning a single-valued tuple must do this, which seems hacky and repetitive.

Is there some elegant general solution to this problem?

A: 

for your first problem you could check if the return value is tuple using

type(r) is tuple
#alternative
isinstance(r, tuple)
# one-liner
def as_tuple(r): return [ tuple([r]), r ][type(r) is tuple]

the second thing i like to use tuple([1]). think it is a matter of taste. could probably also write a wrapper, for example def tuple1(s): return tuple([s])

aaa
+1  A: 

Rather than checking for a length of 1, I'd use the isinstance built-in instead.

>>> isinstance('a_str', tuple)
False
>>> isinstance(('str1', 'str2', 'str3'), tuple)
True
Josh Wright
+4  A: 

For your first problem, I'm not really sure if this is the best answer, but I think you need to check yourself whether the returned value is a string or tuple and act accordingly.

As for your second problem, any variable can be turned into a single valued tuple by placing a , next to it:

>>> x='abc'
>>> x
'abc'
>>> tpl=x,
>>> tpl
('abc',)

Putting these two ideas together:

>>> def make_tuple(k):
...     if isinstance(k,tuple):
...             return k
...     else:
...             return k,
... 
>>> make_tuple('xyz')
('xyz',)
>>> make_tuple(('abc','xyz'))
('abc', 'xyz')

Note: IMHO it is generally a bad idea to use isinstance, or any other form of logic that needs to check the type of an object at runtime. But for this problem I don't see any way around it.

MAK
+7  A: 

You need to somehow test for the type, if it's a string or a tuple. I'd do it like this:

keywords = library.get_keywords()
if not isinstance(keywords, tuple):
    keywords = (keywords,) # Note the comma
for keyword in keywords:
    do_your_thang(keyword)
Lennart Regebro
This works, provided that lists are supposed to be wrapped in one-tuples.
jcdyer
@jcd: They are. See the first sentence of the question.
Lennart Regebro
@Lennart Regebro: Ok, thanks. I was wary about checking type, but there doesn't seem to be much alternative. I will probably wrap the libary function with a wrapper like this, and call that from my own code. And raise a bug with the library maintainer. ;)
ire_and_curses
+1  A: 

Your tuple_maker doesn't do what you think it does. An equivalent definition of tuple maker to yours is

def tuple_maker(input):
    return input

What you're seeing is that tuple_maker("a string") returns a string, while tuple_maker(["str1","str2","str3"]) returns a list of strings; neither return a tuple!

Tuples in Python are defined by the presence of commas, not brackets. Thus (1,2) is a tuple containing the values 1 and 2, while (1,) is a tuple containing the single value 1.

To convert a value to a tuple, as others have pointed out, use tuple.

>>> tuple([1])
(1,)
>>> tuple([1,2])
(1,2)
me_and
`tuple()` creates a tuple out of any iterable; not any value. `tuple('abc') ==> ('a', 'b', 'c')`, `tuple(3) ==> error!`
Javier
@me_and: Thanks for the clarification about tuple definition. But Javier highlights the problem I'm having very well (I had already tried using `tuple` before posting here, and discovered it doesn't help me).
ire_and_curses
+1  A: 

Is it absolutely necessary that it returns tuples, or will any iterable do?

import collections
def iterate(keywords):
    if not isinstance(keywords, collections.Iterable):
        yield keywords
    else:
        for keyword in keywords:
            yield keyword


for keyword in iterate(library.get_keywords()):
    print keyword
Epcylon
@Epcylon: +1 - This is an interesting idea for my own functions, but doesn't help me with my 3rd party library.
ire_and_curses
+2  A: 

There's always monkeypatching!

# Store a reference to the real library function
really_get_keywords = library.get_keywords

# Define out patched version of the function, which uses the real
# version above, adjusting its return value as necessary
def patched_get_keywords():
    """Make sure we always get a tuple of keywords."""
    result = really_get_keywords()
    return result if isinstance(result, tuple) else (result,)

# Install the patched version
library.get_keywords = patched_get_keywords

NOTE: This code might burn down your house and sleep with your wife.

Will McCutchen
@Will McCutchen: +1 - A good point! But I think it will be safer to wrap but not install patched version - I don't know enough about the library's internals to guarantee I won't break something else that depends on this behaviour.
ire_and_curses