1

I have not tried decorators yet or functions within functions, but this current un-pythonic method seems a little convoluted. I dont like how I need to repeat myself when I check the return_type (3 times)?

Any suggestions are welcome, especially if the repetion can be dealt with in an elegant way. Please note that I am not that interested in the numerous reference to test if an object is a number as I believe this part is incorporated within my solution. I am interested in addressing the 3x duplication to deal with the return type. Moreover, while I appreciate the locale method is the more rigorous way of dealing with internationalisation, I prefer the simplicity of allowing the caller more flexibility in choosing the characters.

Thanks

def is_number(obj, thousand_sep=',', decimal_sep=None, return_type='b'):
    """ determines if obj is numeric.

    if return_type = b, returns a boolean True/False
    otherwise, it returns the numeric value

    Examples
    --------
    >>> is_number(3)
    True
    >>> is_number('-4.1728')
    True
    >>> is_number('-4.1728', return_type='n')
    -4.1728
    >>> is_number(-5.43)
    True
    >>> is_number("20,000.43")
    True
    >>> is_number("20.000,43", decimal_sep=",", thousand_sep=",")
    True
    >>> is_number("20.000,43", decimal_sep=",", thousand_sep=".", return_type="n")
    20000.43
    >>> is_number('Four')
    False
    # I am a few light years away from working that one out!!!
    """
    try:
        if is_string(obj):
            if decimal_sep is None:
                value = float(obj.replace(thousand_sep, ""))
            else:
                value = float(obj.replace(thousand_sep, "").replace(decimal_sep, "."))
            if return_type.lower() == 'b':
                return True
            else:
                return value
        else:
            value = float(obj)
            if return_type.lower() == 'b':
                return True
            else:
                return value
    except ValueError:
        return False
        if return_type.lower() == 'b':
            return False
        else:
            return None
Bertie
  • 1,163
  • 3
  • 14
  • 26
  • 2
    @FraserGraham Not a duplicate. this post requires much more functionality in order to accomodate different thousand- and decimal-separators. – ApproachingDarknessFish Dec 07 '13 at 00:34
  • 1
    Belongs on http://codereview.stackexchange.com/ – Ryan Haining Dec 07 '13 at 00:41
  • 2
    To be fair, you are _not_ testing if an object is a number; you rather are testing if given _string_ can be interpreted as a number. There can be many more formats (e.g. hex or exponential form). It would be nice if your title reflected the problem you're solving more precisely. – 9000 Dec 07 '13 at 00:45

2 Answers2

3

using regular expressions, you may do:

import re
regex = re.compile( r'[+-]{0,1}\d{1,3}(,\d\d\d)*(\.\d+)*'

now if you have string txt, and do below

regex.sub( '', txt, count=1 )

you will end up with an empty string if that string is a number with , as thousands separator and . as decimal separator.

This method enforces a strict 3 digits thousands separator. so for example 20,0001.43 is not a number because the thousands separator is wrong. 1220,001.43 is not a number either because it is missing a ,.

def numval( txt ):
    import re
    regex = re.compile( r'[+-]{0,1}\d{1,3}(,\d\d\d)*(\.\d+)*'
    if regex.sub( '', txt.strip(' '), count=1 ) != '':
        raise ValueError ( 'not a number' )
    else:
        return float( txt.replace( ',', '' ))
behzad.nouri
  • 74,723
  • 18
  • 126
  • 124
2

I would probably separate the logic ... I think this does what you are trying to do ...

def get_non_base_10(s):
    #support for base 2,8,and 16
    if s.startswith("O") and s[1:].isdigit():
       return int(s[1:],8)
    elif s.startswith("0x") and s[2:].isdigit():
       return int(s[2:],16)
    elif s.startswith("0b") and s[2:].isdigit():
         return int(s[2:],2)

def get_number(s,decimal_separator=".",thousands_separator=","):
    if isinstance(s,basestring):
       temp_val = get_non_base_10(s)
       if temp_val is not None:
          return temp_val
       s = s.replace(decimal_separator,".").replace(thousands_separator,"")
    try:
       return float(s)
    except ValueError:
       return "nan"

def is_number(s,decimal_separator=".",thousands_separator=",",return_type="b"):
    numeric = get_number(s,decimal_separator,thousands_separator)
    return numeric if return_type != "b" else numeric != "nan"
Joran Beasley
  • 110,522
  • 12
  • 160
  • 179
  • It never occured to me the return if else pattern could work. Thanks and ingeneous how you have taken it to other base number systems. Thanks as it does remove the duplication. – Bertie Dec 07 '13 at 01:07