How to parse a number as either an int or a float, depending on required precision?
Question: Requirements:

  1. Input can be either string or number 
  2. If input could be treated as an int without loss of precision, cast to int 
  3. If input could be treated as a float, cast to float

Here is the section of code where I am using this.

    
    
    def make_operand(symbol, left=None, right=None):
        valid_symbols = ['*', '/', '+', '-']
        if symbol in valid_symbols:
            return Operand(symbol, left, right)
    
        as_int = re.compile("^-?[0-9]+$").match(str(symbol))
        as_float = re.compile("^[-+]?[0-9]*\.?[0-9]+$").match(str(symbol))
    
        as_number = int(symbol) if as_int else float(symbol) if as_float else None
    
        if as_number:
            return NumericOperand(as_number)
    
        raise ValueError("Invalid symbol or number")
    

This works but it looks messy and smells wrong.

An implementation using try blocks also works, but seems less simple:

    
    
        as_number = None
        try:
            as_float = float(symbol)
        except ValueError:
            as_float = None
    
        if as_float:
            as_int = int(as_float)
            as_number = as_int if as_int == as_float else as_float
    
        if as_number:
            return NumericOperand(as_number)
    
        raise ValueError("Invalid symbol or number")
    

Is there a better approach, or is one of these close to the Pythonic method of
doing things?


Answer: If you don't mind third party modules, there is a C-exension module called
[fastnumbers](https://pypi.python.org/pypi/fastnumbers) that was designed
exactly for this purpose. The
[fast_real](http://pythonhosted.org/fastnumbers/fast.html#fast-real) function
does exactly what you are looking for (provided you use `coerce=True`,
available on `fastnumbers>=0.7.4`).

Full disclosure, I am the author.

    
    
    >>> from fastnumbers import fast_real
    >>> fast_real('56')
    56
    >>> fast_real('56.0')
    56
    >>> fast_real('56.07')
    56.07
    >>> fast_real('56.07 lb')
    '56.07 lb'
    >>> fast_real(56.07)
    56.07
    >>> fast_real(56.0)
    56.0
    >>> fast_real(56.0, coerce=True)
    56
    >>> fast_real(56)
    56
    >>> fast_real('56.07 lb', raise_on_invalid=True) 
    Traceback (most recent call last):
      ...
    ValueError: could not convert string to float: '56.07 lb'
    

