2

There's a question on Idiomatic Python - checking for zero but this question is consider with also checking the type of variables within the conditions.

Given the 0 if not variable else variable style statement, it will let nully object slip through, e.g.

>>> x, y = None, []
>>> 0 if not(x and y) else x / y
0
>>> x, y = None, 0
>>> 0 if not(x and y) else x / y
0
>>> x, y = 0, 1
>>> 0 if not(x and y) else x / y
0
>>> x, y = 2, ""
>>> 0 if not(x and y) else x / y
0
>>> x, y = 2, 1
>>> 0 if not(x and y) else x / y
2

But if I explicitly check for variables' value to be zero, it will be somewhat better since it raises error when both types are different or types that cannot be compared to the zero value, e.g.:

>>> x, y = 2, ""
>>> 0 if (x&y) == 0 else x / y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for |: 'int' and 'str'
>>> x,y = "",""
>>> 0 if (x&y) == 0 else x / y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for |: 'str' and 'str'
>>> x,y = [],[]
>>> 0 if (x&y) == 0 else x / y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for |: 'list' and 'list'

So normally when defining a condition to check for numerical value, is something like the 0 if (x|y) == 0 else x/y code more pythonic / more appropriate?

But it's also problematic since it let's boolean type slip through and that causes some quite disturbing thing like ZeroDivisionError or ValueError to occur, e.g.:

>>> x,y = True, True
>>> 0 if (x&y) == 0 else x / y
1
>>> x,y = True, False
>>> 0 if (x&y) == 0 else x / y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
>>> x,y = False, True
>>> 0 if (x&y) == 0 else x / y
0
>>> x,y = False, True
>>> 0 if (x&y) == 0 else math.log(x/y)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: math domain error

Also, this causes problem when types of variable are numerical but somewhat different:

>>> x, y = 1, 3.
>>> 0 if (x|y) == 0 else math.log(x/y)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for |: 'int' and 'float'

And also the fact that the _or operator can't take float, it's weird:

>>> x, y = 1., 3.
>>> 0 if (x&y) == 0 and type(x) == type(y) == float else math.log(x/y)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for |: 'float' and 'float'
>>> x, y = 1., 3.
>>> 0 if (x&y) == 0. and type(x) == type(y) == float else math.log(x/y)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for |: 'float' and 'float'
>>> x, y = 1, 3
>>> 0 if (x&y) == 0 and type(x) == type(y) == float else math.log(x/y)
-1.0986122886681098

So the question is:

  • What is the pythonic way to check for multiple variable for the value zero?
  • Also, it's important to not like boolean type slip and raise a error instead of letting it return zero, how can this be done?
  • And how should the TypeError: unsupported operand type(s) for |: 'float' and 'float' be resolved for checking (x|y) == 0 with x and y as float types?
Community
  • 1
  • 1
alvas
  • 115,346
  • 109
  • 446
  • 738
  • 4
    If you have numerical operations that depends on the exact type of both operands, the obvious (hence pythonic) solution is to either explicitely check the types of both operands or handle expected exceptions. – bruno desthuilliers Jan 15 '16 at 11:24
  • 1
    what about explicit check: "0 in (x,y)" ? – Alex Pertsev Jan 15 '16 at 11:25
  • @A.Haaji this will fail on booleans : `0 in (True, False) => True` – bruno desthuilliers Jan 15 '16 at 11:26
  • Is there a difference between `0 in (x,y)` and `not (x and y)`? – alvas Jan 15 '16 at 11:28
  • 1
    How about `any(i is 0 for i in (x,y))` ? – M4rtini Jan 15 '16 at 11:30
  • @M4rtini: Using `is` on numbers is rarely a good idea. Also note that `0.0 is 0` evaluates to `False`, which is (probably) not what alvas wants. – PM 2Ring Jan 15 '16 at 11:36
  • @PM2Ring Good point, and using == would let boolean types pass through. `any(isinstance(i, numbers.Number) and i == 0 for i in (x,y))` This may be good enough? [ref](http://stackoverflow.com/questions/3441358/python-most-pythonic-way-to-check-if-an-object-is-a-number) – M4rtini Jan 15 '16 at 11:42
  • You know that `(x|y) == 0` is only true if both `x` and `y` equal `0`, right? Maybe you meant `x==0 or y==0`? Bear in mind that `|` is a bitwise operator, so it only makes sense to use it on ints (and booleans, which behave like ints), which is why it raises a `TypeError` if you try to use it with a float operand. – PM 2Ring Jan 15 '16 at 11:42
  • Yes, there's a difference between `0 in (x,y)` and `not (x and y)`, since the latter will evaluate to True if `x` or `y` is any False-y thing. – PM 2Ring Jan 15 '16 at 11:48
  • @PM2Ring Ah, it should have been `(x&y) == 0`!! – alvas Jan 15 '16 at 11:54
  • I don't understand why you want to combine these tests for zero into a single test, since zero in the numerator is quite different to zero in the denominator. `x / 0` is undefined for any non-zero `x`, but `0 / 0` is _undetermined_, which is slightly different to undefined. And of course, `0 / y` is zero for any non-zero `y`. – PM 2Ring Jan 15 '16 at 11:55
  • That makes more sense. :) But with `x&y` you still get a TypeError if `x` or `y` is a float. Is that ok? – PM 2Ring Jan 15 '16 at 11:57
  • Nope, i'm checking for numerical types that's why I'm trying to look for some other solutions to check for types outside of `(x&y)` – alvas Jan 15 '16 at 11:58
  • Does anyone know whether there's a way to include some type hints to my variables? https://www.python.org/dev/peps/pep-0484/ , then checking for values would have by default checked for types. – alvas Jan 15 '16 at 12:01
  • Why not just use the [EAFP](http://stackoverflow.com/q/11360858/4014959) approach, and perform your division in a `try:..except` block that catches `ZeroDivisionError` and `ValueError`, as suggested by Bruno? – PM 2Ring Jan 15 '16 at 12:08

1 Answers1

4

With PEP 20's "Simple is better than complex" in mind, I would claim that if you want to check whether a value has a numeric type other than boolean (mind: Boolean is a numeric type in Python, and 1/True is valid), the most pythonic way to do that is to do exactly that, explicitly, without any bitwise operations or relying on implicit checks.

import numbers

if not isinstance(y, numbers.Number) or type(y) is bool:
    raise TypeError("y must be a number")
return x / y if y else 0
Phillip
  • 13,448
  • 29
  • 41