0

Note: despite the linked duplicate not being a duplicate (it explicitly asks about casting to int and float separately) I still mark it as duplicate because it contains wonderful information about the subject (and an answer similar to my solution)

Summary: I am looking for a pythonic way to convert a string into a number (an integer or a float).

I currently use a waterfall function but while it works, it is not particularly aesthetically pleasant:

def convert_to_int_or_float(a):
    # maybe an integer
    try:
        return int(a)
    except ValueError:
        # nope - so can be a float or a str
        try:
            return float(a)
        except ValueError:
            # must be a str
            return a

for s in ["0", "12", "-13", "14.7", "-14.7", "hello"]:
    print(s, type(convert_to_int_or_float(s)))

# outputs 
# 0 <class 'int'>
# 12 <class 'int'>
# -13 <class 'int'>
# 14.7 <class 'float'>
# -14.7 <class 'float'>
# hello <class 'str'>

Is there a better way to do that? (ideally though a module)

I had look at math but did not find anything there.

WoJ
  • 27,165
  • 48
  • 180
  • 345
  • 1
    What’s the use-case for that? How does the caller of this function consume values of variable, indeterminate types? (I can think of a few uses, but only a few, and in general this is probably not a good idea.) – Konrad Rudolph Aug 26 '20 at 14:38
  • 1
    The fact that `convert_to_int_or_float` can return a `str` (or anything non-numeric) is pretty misleading. – 0x5453 Aug 26 '20 at 14:40
  • @KonradRudolph: yes, this is more or less that. The data comes from a home automation system and the value of the 'state' is sometimes and str, sometimes an int and sometimes a float - while the actual meaning of the value is a number. I have to live with that data stream. – WoJ Aug 26 '20 at 14:41
  • Is there ever a case where it matters that the value is an `int` instead of a `float`? In other words, could you just try converting to `float` and be done with it? – 0x5453 Aug 26 '20 at 14:43
  • @0x5453: yes, it matters because the interpretation of the number is sometimes a float (say, temperature) and sometimes an int (say, the number of open doors). And yes - the name of the function is misleading, I will change that :) – WoJ Aug 26 '20 at 14:44
  • @WoJ But an `int` doesn’t tell you whether it’s a number of doors or a number of cats. The type is still underspecified. Not trying to be argumentative but I still don’t see how this would be used; the only use-case I can think of is, self-referentially, to implement an interpreter/translator for a programming language. – Konrad Rudolph Aug 26 '20 at 14:50
  • I think your approach is ok. Keep in mind that this will have unwanted side effects when used with different locales (where the decimal separator is not a dot) – xtothea Aug 26 '20 at 14:50
  • @KonradRudolph: imagine you receive two things: an "entity" and a "state". The entity can be "cats", or "doors" (in which case it will arrive with a state like `"12"` or `12`) and on my graph of "nr of cats in time" I will know (because I built the graph) that I expect an integer. I do not want to have it as a string (because I cannot average for instance) or a as float (because I only deal with complete cats). – WoJ Aug 26 '20 at 14:54
  • @WoJ If you know you’re expecting an `int` (according to your description), why not directly cast to `int` at that point in your graph (however that’s encoded), rather than guessing the type as your function does? – Konrad Rudolph Aug 26 '20 at 15:01
  • @KonradRudolph: I receive a stream of data ("entity" and "state"). I then push these to a DB (Elasticsearch, actually) and from there I graph the cats. The number of "entities" is large, they change in time, etc. so I want to make sure that the "logical type" of the data (the "state" part - int for cats, float for temperature), sometimes turned into a string and sometimes not, end up in Elasticseach with the right type. – WoJ Aug 26 '20 at 15:07

2 Answers2

3

One approach is to use the ast module

Ex:

import ast


def convert_to_int_or_float(a):
    try:
        return ast.literal_eval(a)
    except ValueError:
        return a

for s in ["0", "12", "-13", "14.7", "-14.7", "hello"]:
    print(s, type(convert_to_int_or_float(s)))
Rakesh
  • 81,458
  • 17
  • 76
  • 113
  • How does this work, exactly? I am trying to understand from https://docs.python.org/3/library/ast.html#ast.literal_eval why "float or int string" would be casted into a float or int though that function. – WoJ Aug 26 '20 at 14:50
  • 1
    @WoJ The function evaluates a string as a piece of Python code, under the assumption that the string contains a Python literal. So `0` is parsed as an integer literal, `14.7` is parsed as a floating point literal, and `hello` fails to evaluate as a literal, and is therefore returned as-is. I believe this function is probably exactly what you’re looking for (baffled by the drive-by downvote). – Konrad Rudolph Aug 26 '20 at 14:51
  • 1
    @KonradRudolph: oh yes, this is actually pretty cool! Rakesh: you may want to add that solution to the duplicate-but-not-duplicate linked question, it is really neat, thanks. – WoJ Aug 26 '20 at 15:01
  • Just make sure that all of your input is in a format you expect. e.g. `'1,2'` will return the tuple `(1, 2)`, not the float `1.2`, even if your locale uses comma as the decimal delimiter. – 0x5453 Aug 26 '20 at 15:09
  • This will also strip quotes off of a string. example: `'"Foo"'` becomes `'Foo'` – Axe319 Aug 26 '20 at 15:15
0

This might be slightly better:

def convert_to_int_or_float(a):
    try:
        float(a)
        return eval(a)
    except ValueError:
        # must be a str
        return a

But it will only work with string inputs.

boi
  • 181
  • 1
  • 10