0

I defined the following function to test if an input is a positive int. I plan on using eval(raw_input("...")) so that's why the try-except part is there:

def is_ok(x):           # checks if x is a positive integer
    is_int = False
    is_pos = False
    try:
        if type(eval(x)) == int:
            is_int = True
            if x > 0:
                is_pos = True
            else:
                pass
        else:
            pass

    except NameError:
        is_int = False
        print "not even a number!"

    return is_int, is_pos 

if I try to pass a positive int it would return True, True as expected. It returns False, False and the error message for a non-number.

However, for negative numbers it still returns True for checking if it's positive. For example, adding this after the function :

is_int, is_pos = is_ok("-9")

print is_int, is_pos

running prints: True True

Can't understand why that is and would love your help. Even if there are more efficient ways to accomplish this, I'd still like to understand why it produces True True. Thanks!

(This is somewhat of a followup to: Python: How do I assign 2 values I return from a function with 1 input as values outside the function?)

Community
  • 1
  • 1
Optimesh
  • 2,667
  • 6
  • 22
  • 22
  • 4
    You know that [eval is evil](http://stackoverflow.com/questions/1832940/is-using-eval-in-python-a-bad-practice), don't you? – Matthias Feb 24 '14 at 11:47

5 Answers5

6

Shorten it down a bit:

def is_ok(x):
    is_int = False
    is_pos = False
    try:
        x = float(x)
        # Check if the number is integer or not
        is_int = x.is_integer()
        if x > 0:
            is_pos = True
    except ValueError:
        print("not even a number!")

    return is_int, is_pos

To explain how this works, you can pass a string to int() to convert this to an integer. However, passing an invalid string (like foo) will raise a ValueError. We can catch that and display our "not even a number!" message to the user. Much safer than using eval().

Try not to use eval() unless you absolutely trust the input.

Removing all the variables you can also make it:

def is_ok(x):
    try:
        # Check if is an integer and greater than 0
        return float(x).is_integer(), float(x) > 0
    except ValueError:
        print("not even a number!")
        return False, False

Explanation of why your code didn't work

Your original problem was it was returning True, True for even negative numbers. The problem here is that whilst you were using type(eval(x)) == int you were then not making x an integer object. This then has an unexpected effect when you try and compare a string to an integer later on (if x > 0):

In [9]: "twenty" > 0
Out[9]: True

You can read more about this strange behaviour in this very detailed answer.

If you were to redefine x as your variable:

try:
    x = eval(x)
    if type(x) == int:

Then when you made the comparison it would behave better and return True, False for "-9".

Community
  • 1
  • 1
Ewan
  • 14,592
  • 6
  • 48
  • 62
  • Your code is very elegant. Thank you. Could you still explain what I did wrong in my code that made it print True True for negative numbers ? Thanks again ;) – Optimesh Feb 24 '14 at 12:04
  • Many many thanks. For the reference to the detailed answer as well! – Optimesh Feb 24 '14 at 12:20
  • Ewan, how does your solution handle floats? If x = 9.8 you're setting it to 9 on line 5, no? – Optimesh Feb 24 '14 at 12:25
  • @Optimesh - yes indeed, it doesn't handle floats. If you want to be able to accept floats it's an easy change, `x = float(x)`. I was unsure if you wanted this or not and chose `int()` as your examples seemed to be integers. Updated my answer to reflect this. – Ewan Feb 24 '14 at 12:28
  • thank, though I think we may be speaking past eachother on this one. The way I want it to work, floats should return False for is_int (makes sense, no?). Using this: if type(x) == int: instead of x = int(x) (or float(x) ) and properly indenting what follows should do the trick I think. Correct me if I'm wrong though. Thanks! – Optimesh Feb 24 '14 at 14:52
  • @Optimesh - ah I understand you now. 2 tics, I'll update my answer :-) – Ewan Feb 24 '14 at 15:13
  • Thank you. :) could you please explain what you did there? and if possible what was wrong with the change i suggested? – Optimesh Feb 24 '14 at 15:23
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/48281/discussion-between-ewan-and-optimesh) – Ewan Feb 24 '14 at 15:37
3

This all seems needlessly complicated, and using eval on raw_input is a security risk - the user can enter anything they like.

Instead, try something like:

def is_ok(s):
    try:
        i = int(s)
    except ValueError:
        return False, False
    else:
        return True, i >= 0

Example inputs and outputs:

>>> for s in ["foo", "9", "-9"]:
    print s, is_ok(s)


foo (False, False)
9 (True, True)
-9 (True, False)

Your error is here:

if x > 0:

Remember x is still a string, and in Python 2.x all strings will compare > 0.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
0
>>> def is_int_positive(n):
...     try:
...         if n == 0:
...             return True, 'Positive'
...         elif max(0,int(n)):
...             return True, 'Positive'
...         else:
...             return True, 'Negative'
...     except:
...         return False, 'Error'
...
>>> for i in [1, 0, -23, 23, 'a']:
...     print i, is_int_positive(i)
...
1 (True, 'Positive')
0 (True, 'Positive')
-23 (True, 'Negative')
23 (True, 'Positive')
a (False, 'Error')
>>>
James Sapam
  • 16,036
  • 12
  • 50
  • 73
0

why not make it simple

def is_positive(number):
  if number > 0:
   return True
  else:
    if number <= 0:
     return False
-1
def is_ok(x):
    try:
        return type(x)==int, int(x)>0
    except ValueError:
        return False, False

Or:

def is_ok(x):
    try:
        return type(x)==int, 'Positive' if int(x)>0 else 'Negative'
    except ValueError:
        return False, 'Neither'
Knowbody
  • 66
  • 5