LBYL vs EAFP

Oscar Benjamin oscar.j.benjamin at gmail.com
Mon Feb 4 20:00:31 EST 2013


On 4 February 2013 23:16, Steven D'Aprano
<steve+comp.lang.python at pearwood.info> wrote:
>
> I want to check that a value is a number. Let's say I don't care what sort
> of number -- float, int, complex, Fraction, Decimal, something else -- just
> that it is a number. Should I:
>
> Look Before I Leap:
>
>     from numbers import Number
>     if isinstance(x, Number):
>         ...
>     else:
>         raise TypeError
>
>
> or Ask Forgiveness:
>
>     x + 0
>     ...

One example that passes in this case but would fail in many others is
a numpy array:

>>> from numpy import array
>>> a = array([1, 2, 3])
>>> a + 0
array([1, 2, 3])
>>> range(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: only length-1 arrays can be converted to Python scalars
>>> bool(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: The truth value of an array with more than one element is
ambiguous. Use a.any() or a.all()

>
>
> where in both cases the ellipsis ... is the code I actually care about.
>
> The second version is more compact and easier to write, but is it easier to
> read?
>
> Does your answer change if I then go on to check the range of the number,
> e.g. to test that x is positive?
>
> A third option is not to check x at all, and hope that it will blow up at
> some arbitrary place in the middle of my code rather than silently do the
> wrong thing. I don't like this idea because, even if it fails, it is better
> to fail earlier than later.

Why would a non-number appear in this code? How serious a problem
would it be if it did happen?

The other question is whether there is likely to be a use-case for
allowing non-standard numeric types (apart from dogmatic support of
duck-typing).

I'm imagining a scenario where you wrote a library and a user (another
programmer) passed in the wrong thing (even though you clearly
documented that you wanted a number).

If you do check then they get this:

# myuserscript.py
import stevesmod
stevesmod.calculate_with_numbers(number=file)

$ python myuserscript.py
Traceback (most recent call last):
  File "myuserscript.py", line 5, in <module>
    stevesmod.calculate_with_numbers(number=file)
  File ".local/pymodules/stevesmod.py", line 5, in calculate_with_numbers
    x + 0
TypeError: unsupported operand type(s) for +: 'type' and 'int'

If you don't check then they get:

$ python myuserscript.py
Traceback (most recent call last):
  File "myuserscript.py", line 5, in <module>
    stevesmod.calculate_with_numbers(number=file)
  File "/home/user/.local/pymodules/stevesmod.py", line 7, in
calculate_with_numbers
    compute_stuff(number, other_parameters)
  File "/home/user/.local/pymodules/stevesmod.py", line 26, in compute_stuff
    normalise(nx, ny)
  File "/home/user/.local/pymodules/stevesmod.py", line 35, in normalise
    nx += 1
TypeError: unsupported operand type(s) for +: 'type' and 'int'


>From the users perspective the traceback gets more complicated. They
still see the exact point at which there code calls into yours though.
So if they know how to use a debugger and reread your docs then it's
fine. (If you were doing something callback-based or storing the
values for later use it would get more complicated).

On the other hand if they just end up getting nonsensical return
values then they should be able to trace that back.


Oscar



More information about the Python-list mailing list