1

Is there an easy and obvious way to convert a string to a number of whatever type it looks like as a string, in other words I'd like to retain its type, whether it looks like int, float or complex and convert from string to number. This would be handy for example when reading data from text file. Usually conversion is made from string to a certain type of number for example a=int('2.2'). The downside of this is that 2.2 is not int and it will be cut to 2. Is there an easy and obvious way to let the code decide which type of number it is and convert to that type?

A similar question How do I parse a string to float or int in Python is a basic question of how to convert to int or float. My question went further and asked the code to decide which type of number it is.

A similar question How can I convert a string to either int or float with priority of int is a little hard to understand what the questions is, and the accepted answer does not cover my case.

PlasticDuck
  • 105
  • 1
  • 6
  • I see numerous answers on the first page you linked that should answer your question as well. – Subbeh May 31 '18 at 05:56
  • People down vvv there are having trouble understanding that you need to support complex numbers. You may want to put that in bold or something. – Aran-Fey May 31 '18 at 06:21

3 Answers3

1

Option 1

Try to convert the string to an int, if that fails try to convert it to float, and then complex:

def parse_number(num):
    for cls in [int, float, complex]:
        try:
            return cls(num)
        except ValueError:
            pass
    raise ValueError("Could not convert {} to a number".format(num))
>>> nums = ['5.0', '2', '3-2.5j']
>>> [parse_number(x) for x in nums]
[5.0, 2, (3-2.5j)]

Option 2

Use ast.literal_eval to parse the string, then check if the result was a number:

import ast
from numbers import Number

def parse_number(num):
    result = ast.literal_eval(num)
    if isinstance(result, Number):
        return result

    raise ValueError("Could not convert {} to a number".format(num))
Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
0

Use asts literal_eval:

In [118]: import ast

In [119]: ast.literal_eval('1')
Out[119]: 1

In [120]: ast.literal_eval('1.1')
Out[120]: 1.1

In [121]: type(ast.literal_eval('1'))
Out[121]: int

In [122]: type(ast.literal_eval('1.1'))
Out[122]: float

In [123]: ast.literal_eval('1j')
Out[123]: 1j

In [124]: type(ast.literal_eval('1j'))
Out[124]: complex
Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
aydow
  • 3,673
  • 2
  • 23
  • 40
0

Another approach using regex and literal_eval from ast module:

import re
from ast import literal_eval as le

def search(string):
    try:
        pattern = re.compile(
            r'(?P<int>[+-]?\d+$)'
            r'|(?P<float>[+-]?\d+[\.,]\d+$)'
            r'|(?P<complex>[+-]?(\d+[\.,]\d+|\d+)j?[+-](\d+[\.,]\d+|\d+)j?$)'
        )
        for group, val in pattern.search(string).groupdict().items():
            if val != None:
                return string, group, le(val), 'None'
    except Exception as e:
        return string, '', '', 'cannot catch it!'


# Test
tests = [
            '123', '123.34', '10.0',
            '-123', '+123', '-10.5', 
            '+10.3', '+4-6j', '+3.1j+1.1', 
            '4.3-5j', '-4+2.1j'
        ]

fmt = '  {:<15}\t{:<15}\t{:<15}\t{:<15}'
print(fmt.format('STRING', 'TYPE', 'CAPTURED', 'ERROR') + '\n')

for test in tests:
    string, group, val, error = search(test)
    print(fmt.format(string, group, val, error))

Output:

  STRING            TYPE            CAPTURED        ERROR          

  123               int             123             None           
  123.34            float           123.34          None           
  10.0              float           10.0            None           
  -123              int             -123            None           
  +123              int             123             None           
  -10.5             float           -10.5           None           
  +10.3             float           10.3            None           
  +4-6j             complex         (4-6j)          None           
  +3.1j+1.1         complex         (1.1+3.1j)      None           
  4.3-5j            complex         (4.3-5j)        None           
  -4+2.1j           complex         (-4+2.1j)       None 

And, for sure, you can improve the regex part by adding another patterns.

Chiheb Nexus
  • 9,104
  • 4
  • 30
  • 43