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?
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)
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.
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)
The ()
have nothing to do with tuples in python, the tuple syntax uses ,
. The ()
-s are optional.
E.g.:
>>> a=1, 2, 3
>>> type(a)
<class 'tuple'>
>>> a=1,
>>> type(a)
<class 'tuple'>
>>> a=(1)
>>> type(a)
<class 'int'>
I guess this is the root of the problem.
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.
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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With