13

I couldn't find another answer when I wanted this, so I thought I would post my own solution for anyone else and also get corrections if I've done something wrong.

I had to make an automatic config file parser and I preferred to make numbers int if possible and float if not. The usual try/except conversion doesn't work by itself since any float will just be coerced to an int.

To clarify as someone asked, the case is basically to convert numbers as the person writing the config file data intended them to be. So anything with decimals would probably be intended to be a float. Also, I believe that float can be dangerous for some operators (e.g. ==, <, >) due to the nature of floating point numbers, and int will be converted to float when necessary. Therefore I prefer numbers to stay in int when possible. Not a big thing, just as a kind of convention for myself.

KobeJohn
  • 7,390
  • 6
  • 41
  • 62
  • 1
    possible duplicate of [Python - Parse String to Float or Int](http://stackoverflow.com/questions/379906/python-parse-string-to-float-or-int) – johnsyweb Apr 09 '11 at 23:57
  • NOT a duplicate; that question asks for something different. – John Machin Apr 10 '11 at 01:34
  • 1
    Edit your question to give examples that make it clear what you mean by "make numbers int if possible". In particular, do you want "10.0000" (where the user's intention is obviously float) converted to `int` or `float`. – John Machin Apr 10 '11 at 02:43
  • 1
    Why do you want an "int if possible" if floats are valid anyway? – detly Apr 10 '11 at 03:41

5 Answers5

25
def int_or_float(s):
    try:
        return int(s)
    except ValueError:
        return float(s)

If you want something like "10.0000" converted to int, try this:

def int_dammit_else_float(s):
    f = float(s)
    i = int(f)
    return i if i == f else f

What result do you want for input like "1e25"?

John Machin
  • 81,303
  • 11
  • 141
  • 189
  • Wow I felt silly when I realized that your first solution does exactly what I wanted. I was working under the mistaken understanding that int('1.1') would coerce to 1 since int(1.1) does. Thanks for the answer and helping me correct my wrong assumption. – KobeJohn Apr 10 '11 at 14:26
3

This is one time where it's probably better to get permission first than ask for forgiveness later which is usually considered the 'Pythonic' way to do things and usually involves try/except. So something simple like this might be good enough:

v = float(s) if '.' in s or 'e' in s.lower() else int(s) 

Since there's more than one character that can signal a floating point number, you could also check for one of them these following other ways:

import re
pattern = re.compile(r'[.eE]')
v = float(s) if pattern.findall(s) else int(s)

or

chars = set('.eE')
v = float(s) if any((c in chars) for c in s) else int(s)

Lastly, on a slightly different track, if you wanted floating point whole numbers to become integers, you could do the following, which is similar to @John Machin's "_dammit_" function:

v = int(float(s)) if int(float(s)) == float(s) else float(s)
martineau
  • 119,623
  • 25
  • 170
  • 301
  • Only if you know everything that you should be asking; try "1E-6" – John Machin Apr 10 '11 at 02:02
  • @John Machin: Good point in general, but here probably still manageable (see my update). – martineau Apr 10 '11 at 02:14
  • Thanks for the code martineau. It will work very well for my needs and simplifies what I had. I will actually leave out the E notation because once you start handling cases, I think it would be better just to use a regular expression to catch all the cases. – KobeJohn Apr 10 '11 at 11:59
  • @yakiimo: Yeah, I thought that might be the case, as Albert Einstein said, "Make everything as simple as possible, but not simpler" and "Any fool can make things bigger, more complex, and more violent. It takes a touch of genius -- and a lot of courage -- to move in the opposite direction." There's also "Do the simplest thing that could possibly work" (which he didn't say). – martineau Apr 10 '11 at 12:29
  • @yakiimo, you're welcome! When you've received enough answers and tried them out, if one has helped you, be sure to accept it (by clicking the checkmark-shaped icon next to the answer) -- it's fundamental SO etiquette!-) – martineau Apr 10 '11 at 12:46
2

eval(s)

eval() is a function that accepts a string and processes it as Python code. When given any kind of literal as a string, it will return a Python object containing that value.

However, from a security standpoint, this is a bad idea, as someone could potentially feed instructions that crash or tamper with your program. You could require it to be a certain set of characters before processing, i.e.:

eval(s) if not set(s).difference('0123456789.') else None

This still probably isn't worth the headache, though. That said, you can get fancy, and allow expressions with calculations as well... and that might be worth trying!

num = lambda s: eval(s) if not set(s).difference('0123456789. *+-/e') else None

>>> num('10')
10
>>> num('10.0')
10.0
>>> num('1e3')
1000.0
>>> num('4.8 * 10**34')
4.8e+34
>>> num('1+2/3')
1.6666666666666665
Community
  • 1
  • 1
krs013
  • 2,799
  • 1
  • 17
  • 17
2
def int_float_none(x):
    # it may be already int or float 
    if isinstance(x, (int, float)):
        return x
    # all int like strings can be converted to float so int tries first 
    try:
        return int(x)
    except (TypeError, ValueError):
        pass
    try:
        return float(x)
    except (TypeError, ValueError):
        return None

Function above for any object passed will return int or float conversion or None.

Jacek Błocki
  • 452
  • 3
  • 9
0

Trying to detect a float isn't easy, but detecting a possible integer is easier.

I would try to check if the string contains only digits (disregarding possible leading spaces or sign) and convert to float or integer accordingly:

lst = ['1' , '2.2' , ' -3','+5']

result = [int(x) if x.lstrip(" -+").isdigit() else float(x) for x in lst]

print(result)

prints:

[1, 2.2, -3, 5]
Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219