views:

1212

answers:

10

How can I convert strings which can denote decimal or rational numbers to floats

>>> ["0.1234", "1/2"]
['0.1234', '1/2']

I'd want [0.1234, 0.5].

eval is what I was thinking but no luck:

>>> eval("1/2")
0
+2  A: 

The / operator does integer division. Try:

>>> eval("1.0*" + "1/2")
0.5

Because eval() is potentially dangerous, you should always check precisely what you are passing into it:

>>> import re
>>> s = "1/2"
>>> if re.match(r"\d+/\d+$", s):
...     eval("1.0*" + s)
...
0.5

However, if you go to the trouble of matching the input against a regex in the first place, you might as well use r"(\d+)/(\d+)$" to extract the numerator and denominator, do the division yourself, and entirely avoid eval():

>>> m = re.match(r"(\d+)/(\d+)$", s)
>>> if m:
...     float(m.group(1)) / float(m.group(2))
...
0.5
Greg Hewgill
Thanks, the eval hint is nice to now and would do the job just great... except that I realized I need a safe solution as pointed out.
ketorin
A: 

That's because 1 and 2 are interpreted by Python as integers and not floats. It needs to be 1.0/2.0 or some mix of that.

MrTopf
+3  A: 

Use from __future__ import division to get the behavior you want. Then, in a pinch, you can do something like

from __future__ import division
strings = ["0.1234", "1/2", "2/3"]
numbers = map(eval, strings)

to get a list of floats out of your strings. If you want to do this the "right" way, don't use eval(), but instead write a function that accepts a string and calls float() on it if it contains no slash, or parses the string and divides the numerator and denominator if there's a slash in it.

One way to do it:

def parse_float_string(x)
    parts = x.split('/', 1)
    if len(parts) == 1:
        return float(x)
    elif len(parts) == 2:
        return float(parts[0])/float(parts[1])
    else:
        raise ValueError

Then just map(parse_float_string, strings) will get you your list.

kquinn
+1  A: 

The problem with eval is that, as in python, the quotient of integers is an integer. So, you have several choices.

The first is simply to make integer division return floats:

from __future__ import division

The other is to split the rational number:

reduce(lambda x, y: x*y, map(int, rat_str.split("/")), 1)

Where rat_str is the string with a rational number.

pavpanchekha
+9  A: 

I'd parse the string if conversion fails:

>>> def convert(s):
    try:
        return float(s)
    except ValueError:
        num, denom = s.split('/')
        return float(num) / float(denom)
...

>>> convert("0.1234")
0.1234

>>> convert("1/2")
0.5

Generally using eval is a bad idea, since it's a security risk. Especially if the string being evaluated came from outside the system.

Ryan
+1. eval is completely unnecessary in this case, and I'm glad somebody (other than me, of course) stuck up for what's right.
Devin Jeanpierre
Eval is not a security risk unless your co-workers are malicious sociopaths. You don't need it in this case, but it's no more a security risk than open source code itself.
S.Lott
Very good. The string is indeed from outside so I need to go safe (and try not to learn bad habits). I was hoping for not needing to parse it but this is not too bad and works like a charm.
ketorin
A: 

The suggestions with from __future__ import division combined with eval will certainly work.

It's probably worth pointing out that the suggestions that don't use eval but rather parse the string do so because eval is dangerous: if there is some way for an arbitrary string to get sent to eval, then your system is vulnerable. So it's a bad habit. (But if this is just quick and dirty code, it's probably not that big a deal!)

Andrew Jaffe
+6  A: 

As others have pointed out, using eval is potentially a security risk, and certainly a bad habit to get into. (if you don't think it's as risky as exec, imagine evaling something like: __import__('os').system('rm -rf /'))

However, if you have python 2.6 or up, you can use ast.literal_eval, for which the string provided:

may only consist of the following Python literal structures: strings, numbers, tuples, lists, dicts, booleans, and None.

Thus it should be quite safe :-)

John Fouhy
Didn't know about that -- thanks!
Andrew Jaffe
How is it possible to eval something as malicious as the example without having coworkers who are seriously deranged? Eval is no more a security risk than access to the shell is a security risk.
S.Lott
Fair point..If you're not dealing with untrusted input then eval is no risk. I think I'm influenced by this post: http://phpxmlrpc.sourceforge.net/#security -- PHP guys tried twice to solve their problems, before realising that they had to get rid of eval. So ...
John Fouhy
...I'm inclined to take the position that the easiest way to avoid problems is to get into the habit of using it never. Then, if I do find myself needing eval, at least I've got the mental check of breaking one of my rules, which makes me look harder at what I'm doing.
John Fouhy
Ok, point well taken why to avoid eval.
ketorin
+5  A: 

Another option (also only for 2.6 and up) is the fractions module.

>>> from fractions import Fraction
>>> Fraction("0.1234")
Fraction(617, 5000)
>>> Fraction("1/2")
Fraction(1, 2)
>>> float(Fraction("0.1234"))
0.1234
>>> float(Fraction("1/2"))
0.5
pantsgolem
A: 

In Python 3, this should work.

>>> x = ["0.1234", "1/2"]
>>> [eval(i) for i in x]
[0.1234, 0.5]
Selinap
Right, in Python 3 they made `from __future__ import division` the default... in Python 3, the future is now!
kquinn
A: 

sympy can help you out here:

import sympy

half = sympy.Rational('1/2')
p1234 = sympy.Rational('0.1234')
print '%f, %f" % (half, p1234)
Ignacio Vazquez-Abrams