Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #38147 > unrolled thread

LBYL vs EAFP

Started bySteven D'Aprano <steve+comp.lang.python@pearwood.info>
First post2013-02-05 10:16 +1100
Last post2013-02-05 02:53 -0500
Articles 17 — 8 participants

Back to article view | Back to comp.lang.python


Contents

  LBYL vs EAFP Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-02-05 10:16 +1100
    Re: LBYL vs EAFP Chris Angelico <rosuav@gmail.com> - 2013-02-05 10:38 +1100
      Re: LBYL vs EAFP Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-02-05 03:52 +0000
        Re: LBYL vs EAFP Chris Angelico <rosuav@gmail.com> - 2013-02-05 16:19 +1100
    Re: LBYL vs EAFP Ian Kelly <ian.g.kelly@gmail.com> - 2013-02-04 16:46 -0700
      Re: LBYL vs EAFP Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-02-05 04:52 +0000
        Re: LBYL vs EAFP Chris Angelico <rosuav@gmail.com> - 2013-02-05 16:20 +1100
          Re: LBYL vs EAFP Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-02-05 06:31 +0000
            Re: LBYL vs EAFP Pete Forman <petef4+usenet@gmail.com> - 2013-02-05 09:49 +0000
              Re: LBYL vs EAFP Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-02-05 23:04 +1100
                Re: LBYL vs EAFP Chris Angelico <rosuav@gmail.com> - 2013-02-05 23:25 +1100
        Re: LBYL vs EAFP Ian Kelly <ian.g.kelly@gmail.com> - 2013-02-04 22:40 -0700
    Re: LBYL vs EAFP Dave Angel <davea@davea.name> - 2013-02-04 18:55 -0500
    Re: LBYL vs EAFP Chris Angelico <rosuav@gmail.com> - 2013-02-05 11:45 +1100
    Re: LBYL vs EAFP Ethan Furman <ethan@stoneleaf.us> - 2013-02-04 16:26 -0800
    Re: LBYL vs EAFP Oscar Benjamin <oscar.j.benjamin@gmail.com> - 2013-02-05 01:00 +0000
    Re: LBYL vs EAFP Terry Reedy <tjreedy@udel.edu> - 2013-02-05 02:53 -0500

#38147 — LBYL vs EAFP

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2013-02-05 10:16 +1100
SubjectLBYL vs EAFP
Message-ID<5110415c$0$29986$c3e8da3$5496439d@news.astraweb.com>
The eternal conflict between "Look Before You Leap" and "Easier to Ask for
Forgiveness than Permission" (LBYL vs EAFP) continues... 

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
    ...
        

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.

Comments, thoughts and opinions please.



-- 
Steven

[toc] | [next] | [standalone]


#38148

FromChris Angelico <rosuav@gmail.com>
Date2013-02-05 10:38 +1100
Message-ID<mailman.1338.1360021130.2939.python-list@python.org>
In reply to#38147
On Tue, Feb 5, 2013 at 10:16 AM, Steven D'Aprano
<steve+comp.lang.python@pearwood.info> wrote:
> 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.
>
> Comments, thoughts and opinions please.

It depends on what could cause the failure. If only a programming
error could cause x to not be a number, I'd go with your third option
- let it blow up anywhere, and follow the trace. That option requires
zero forethought, which translates directly into programmer
efficiency. But if x came from a user,  then I'd be checking inputs at
a much earlier point.

Those are the two obvious cases though, and I'm assuming your
situation is neither of them. Writing library code is half way in
between. With the specific examples given, I wouldn't like to use "x +
0" as a check; it seems dodgy. Firstly because it doesn't look like a
data type check (though a comment can help with that), and secondly
because something might very well support having a number added to it
while definitely not itself being a number - eg something along the
lines of a database cursor. So I would recommend LBYL for this
particular case.

ChrisA

[toc] | [prev] | [next] | [standalone]


#38158

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2013-02-05 03:52 +0000
Message-ID<511081e3$0$21856$c3e8da3$76491128@news.astraweb.com>
In reply to#38148
On Tue, 05 Feb 2013 10:38:41 +1100, Chris Angelico wrote:

> On Tue, Feb 5, 2013 at 10:16 AM, Steven D'Aprano
> <steve+comp.lang.python@pearwood.info> wrote:
>> 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.
[...]

> With
> the specific examples given, I wouldn't like to use "x + 0" as a check;
> it seems dodgy. Firstly because it doesn't look like a data type check

Strange. To me, it looks like "oh, you're testing whether x supports 
numeric addition". I suppose the hidden assumption I'm making is that if 
x supports addition, it is a kind of number. Perhaps that's an unsafe 
assumption.


> (though a comment can help with that), and secondly because something
> might very well support having a number added to it while definitely not
> itself being a number - eg something along the lines of a database
> cursor.

But surely duck-typing tells us that if database cursors support the same 
sort of operations that numbers support, we should therefore treat them 
as a kind of number?

My library will not try to prevent the caller taking the average of (say) 
a dozen eggs and 10 miles. I'm not sure that it should try to prevent the 
caller from trying to take the average between two database cursors.



-- 
Steven

[toc] | [prev] | [next] | [standalone]


#38162

FromChris Angelico <rosuav@gmail.com>
Date2013-02-05 16:19 +1100
Message-ID<mailman.1349.1360041568.2939.python-list@python.org>
In reply to#38158
On Tue, Feb 5, 2013 at 2:52 PM, Steven D'Aprano
<steve+comp.lang.python@pearwood.info> wrote:
> On Tue, 05 Feb 2013 10:38:41 +1100, Chris Angelico wrote:
>
>> On Tue, Feb 5, 2013 at 10:16 AM, Steven D'Aprano
>> <steve+comp.lang.python@pearwood.info> wrote:
>>> 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.
> [...]
>
>> With
>> the specific examples given, I wouldn't like to use "x + 0" as a check;
>> it seems dodgy. Firstly because it doesn't look like a data type check
>
> Strange. To me, it looks like "oh, you're testing whether x supports
> numeric addition". I suppose the hidden assumption I'm making is that if
> x supports addition, it is a kind of number. Perhaps that's an unsafe
> assumption.

If your code bombs and the exception traceback shows a line saying "x
+ 0", I'm going to wonder if it's a massive bug... or, worse, if
you're expecting x.__add__() to have stupid side effects ("yeah,
adding 0 to x turns x into a float, but subtracting 0 from it forces
it to be an int").

>> (though a comment can help with that), and secondly because something
>> might very well support having a number added to it while definitely not
>> itself being a number - eg something along the lines of a database
>> cursor.
>
> But surely duck-typing tells us that if database cursors support the same
> sort of operations that numbers support, we should therefore treat them
> as a kind of number?

Depends what operations you mean. It's not uncommon for a
cursor/iterator object to support "x+=1" to move to the next
row/element/whatever; if your result set is maintained elsewhere, you
could cheaply support "x + 4" to create a new iterator that's four
rows further along. But in all other respects, it's not a number. You
can't multiply an iterator by 2, for instance.

> My library will not try to prevent the caller taking the average of (say)
> a dozen eggs and 10 miles. I'm not sure that it should try to prevent the
> caller from trying to take the average between two database cursors.

Well, sure... if your code is so simple and trivial, and if the
cursors support "x - y" to get the number of rows between them. But in
that case, go for true EAFP and eschew error checking altogether.

ChrisA

[toc] | [prev] | [next] | [standalone]


#38149

FromIan Kelly <ian.g.kelly@gmail.com>
Date2013-02-04 16:46 -0700
Message-ID<mailman.1339.1360021575.2939.python-list@python.org>
In reply to#38147

[Multipart message — attachments visible in raw view] — view raw

On Feb 4, 2013 4:24 PM, "Steven D&apos;Aprano" <
steve+comp.lang.python@pearwood.info> wrote:
>
> The eternal conflict between "Look Before You Leap" and "Easier to Ask for
> Forgiveness than Permission" (LBYL vs EAFP) continues...
>
> 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
>     ...
>
>
> where in both cases the ellipsis ... is the code I actually care about.

It seems to me that both of these are LBYL. That the second test checks by
trying an operation and potentially raising an exception is immaterial.
You're still performing a test prior to attempting the actual operation.

> 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.

This is what I would consider EAFP. Presumably if the operation requires a
number, then it will at some point perform some kind of numerical
manipulation that will raise a TypeError if one is not passed. If the
operation succeeds, then the object supported all the operations you asked
of it, so in what sense would the program be doing the wrong thing?

[toc] | [prev] | [next] | [standalone]


#38161

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2013-02-05 04:52 +0000
Message-ID<51109001$0$21856$c3e8da3$76491128@news.astraweb.com>
In reply to#38149
On Mon, 04 Feb 2013 16:46:11 -0700, Ian Kelly wrote:

> Presumably if the operation requires
> a number, then it will at some point perform some kind of numerical
> manipulation that will raise a TypeError if one is not passed. If the
> operation succeeds, then the object supported all the operations you
> asked of it, so in what sense would the program be doing the wrong
> thing?

It might not support *all* the operations. Consider a toy function like 
this:

def toy(x):  # Don't try this at home!
    return x*10000000000 + 1

If you pass a non-empty list, Python will:

- try to allocate a chunk of memory of at least 4 GB (estimated), which 
may cause quite a bit of thrashing;

- if somehow this succeeds, then it will create a list and populate it 
with rather a lot of duplicated references;

- at which point it will then try to add a list to an int, and raise an 
exception;

- and then deallocate a huge list, causing more thrashing.


So there are consequences to allowing exceptions to occur in arbitrary 
places.

There's also the principle that it is best to raise an exception as early 
as possible. It's easier to track down errors at the point they are 
introduced than long afterwards.

And finally, even worse than exceptions are silent failures of semantics: 
code that does the wrong thing rather than fail loudly and safely. Just 
because the code doesn't fail, doesn't mean it has worked, and an 
exception is much better than a silent failure. You seem to be making the 
classic mistake of thinking that exceptions are something to avoid:

"I find it amusing when novice programmers believe their main job is 
preventing programs from crashing. [...] More experienced programmers 
realize that correct code is great, code that crashes could use 
improvement, but incorrect code that doesn’t crash is a horrible 
nightmare."  -- Chris Smith

http://cdsmith.wordpress.com/2011/01/09/an-old-article-i-wrote/


Duck-typing is not a panacea, and it too has failure modes. The usual 
example is, suppose you have a graphics application that expects an 
object with a "draw" method:

pencil.draw()
paintbrush.draw()
crayon.draw()
six_shooter.draw()  # bang, you've just shot yourself in the foot


So I lean very strongly to some sort of explicit check ahead of time. I'm 
just not sure *what sort* of explicit check.


-- 
Steven

[toc] | [prev] | [next] | [standalone]


#38163

FromChris Angelico <rosuav@gmail.com>
Date2013-02-05 16:20 +1100
Message-ID<mailman.1350.1360041622.2939.python-list@python.org>
In reply to#38161
On Tue, Feb 5, 2013 at 3:52 PM, Steven D'Aprano
<steve+comp.lang.python@pearwood.info> wrote:
> There's also the principle that it is best to raise an exception as early
> as possible. It's easier to track down errors at the point they are
> introduced than long afterwards.

Yes, definitely, especially (as was mentioned) if you're working with
callbacks. But I'd use isinstance then.

ChrisA

[toc] | [prev] | [next] | [standalone]


#38167

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2013-02-05 06:31 +0000
Message-ID<5110a73a$0$21856$c3e8da3$76491128@news.astraweb.com>
In reply to#38163
On Tue, 05 Feb 2013 16:20:19 +1100, Chris Angelico wrote:

> On Tue, Feb 5, 2013 at 3:52 PM, Steven D'Aprano
> <steve+comp.lang.python@pearwood.info> wrote:
>> There's also the principle that it is best to raise an exception as
>> early as possible. It's easier to track down errors at the point they
>> are introduced than long afterwards.
> 
> Yes, definitely, especially (as was mentioned) if you're working with
> callbacks. But I'd use isinstance then.


I'm leaning towards an isinstance check

I've been using Python since Python 1.5. Even though 1.5 had an 
"isinstance" function, I learned Python from books and code written for 
Python 1.4 which did not have that function, so the usual way to do type-
checking was:


if type(obj) is type(1):
    # it's an int


and strongly discouraged. So my instincts are still very strongly primed 
to *not* do type-checking if I can avoid it. But in this case, I think I 
agree with those suggesting the isinstance check is the right approach. 
The main downside to this is that objects which delegate to a number will 
not work unless they are explicitly registered with the Number ABC.


Thanks to everyone who responded.



-- 
Steven

[toc] | [prev] | [next] | [standalone]


#38178

FromPete Forman <petef4+usenet@gmail.com>
Date2013-02-05 09:49 +0000
Message-ID<867gmnjdkd.fsf@gmail.com>
In reply to#38167
Steven D'Aprano <steve+comp.lang.python@pearwood.info> writes:
>> I want to check that a value is a number. [...]
> I'm leaning towards an isinstance check

Well that is the answer to your question, whether the value *is* a
number. EAFP can answer the question whether the value *behaves* like a
number, where the criterion depends on what your code is aiming to do
with the value.

BTW what if the value is Not-a-Number? ;-)

-- 
Pete Forman

[toc] | [prev] | [next] | [standalone]


#38184

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2013-02-05 23:04 +1100
Message-ID<5110f548$0$29995$c3e8da3$5496439d@news.astraweb.com>
In reply to#38178
Pete Forman wrote:

> Steven D'Aprano <steve+comp.lang.python@pearwood.info> writes:
>>> I want to check that a value is a number. [...]
>> I'm leaning towards an isinstance check
[...]
> BTW what if the value is Not-a-Number? ;-)

Nothing different, and hopefully exactly what the caller expects. As far as
Python is concerned, NANs are Numbers.

py> NAN = float('nan')
py> from numbers import Number
py> isinstance(NAN, Number)
True


If it's a float NAN, Python doesn't give you much control over what happens
next, but generally any arithmetic operation on a NAN will return a NAN
rather than raise.

If it's a Decimal NAN, the same applies: 

py> NAN = decimal.Decimal('nan')
py> NAN + 0
Decimal('NaN')


If it's a Decimal SNAN (signalling NAN), then arithmetic operations signal
InvalidOperation, which by default will raise an exception:

py> SNAN = decimal.Decimal('snan')
py> SNAN + 0
Traceback (most recent call last):
 ...
decimal.InvalidOperation: sNaN




-- 
Steven

[toc] | [prev] | [next] | [standalone]


#38185

FromChris Angelico <rosuav@gmail.com>
Date2013-02-05 23:25 +1100
Message-ID<mailman.1363.1360067128.2939.python-list@python.org>
In reply to#38184
On Tue, Feb 5, 2013 at 11:04 PM, Steven D'Aprano
<steve+comp.lang.python@pearwood.info> wrote:
> py> isinstance(NAN, Number)
> True

Does that line of code count as nerd humour?

ChrisA

[toc] | [prev] | [next] | [standalone]


#38164

FromIan Kelly <ian.g.kelly@gmail.com>
Date2013-02-04 22:40 -0700
Message-ID<mailman.1351.1360042892.2939.python-list@python.org>
In reply to#38161
On Mon, Feb 4, 2013 at 9:52 PM, Steven D'Aprano
<steve+comp.lang.python@pearwood.info> wrote:
> You seem to be making the
> classic mistake of thinking that exceptions are something to avoid:

Far from it.  You've extrapolated a lot more than what I actually
said, and I completely agree with everything you wrote.  I was
explaining EAFP as I see it, not advocating it for all circumstances.

Although since you bring it up, I find that the LBYL crowd tends to be
more prone to exception avoidance, e.g. returning None on a failure
rather than raising an exception, whereas the EAFP crowd seems more
likely to just let the original exception propagate up.

[toc] | [prev] | [next] | [standalone]


#38151

FromDave Angel <davea@davea.name>
Date2013-02-04 18:55 -0500
Message-ID<mailman.1341.1360022138.2939.python-list@python.org>
In reply to#38147
On 02/04/2013 06:38 PM, Chris Angelico wrote:
> On Tue, Feb 5, 2013 at 10:16 AM, Steven D'Aprano
> <steve+comp.lang.python@pearwood.info> wrote:
>> 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.
>>
>> Comments, thoughts and opinions please.
>
> It depends on what could cause the failure. If only a programming
> error could cause x to not be a number, I'd go with your third option
> - let it blow up anywhere, and follow the trace. That option requires
> zero forethought, which translates directly into programmer
> efficiency. But if x came from a user,  then I'd be checking inputs at
> a much earlier point.
>
> Those are the two obvious cases though, and I'm assuming your
> situation is neither of them. Writing library code is half way in
> between. With the specific examples given, I wouldn't like to use "x +
> 0" as a check; it seems dodgy. Firstly because it doesn't look like a
> data type check (though a comment can help with that), and secondly
> because something might very well support having a number added to it
> while definitely not itself being a number - eg something along the
> lines of a database cursor. So I would recommend LBYL for this
> particular case.
>
> ChrisA
>

I agree with Chris.  It would seem that the main purpose of the abc 
numbers.Number is for this.  See the page:

http://docs.python.org/2/library/numbers.html
   "If you just want to check if an argument x is a number, without 
caring what kind, use isinstance(x, Number)."

But the real question is "what is a number?" .   You used lowercase in 
your description, so you weren't defining it as the particular classes 
that were already associated with Number.  I expect you're using Python 
3, which you should have specified.  If so, I'd tend to replace the x+0 
with x > 0    which would be an error for any class other than int which 
didn't define the corresponding dunder operators.


Coincidentally, that's the next question you asked.  So if you're only 
interested in values that are positive, that would seem to be clearer.

Unfortunately, that test would raise a TypeError for complex values.



-- 
DaveA

[toc] | [prev] | [next] | [standalone]


#38153

FromChris Angelico <rosuav@gmail.com>
Date2013-02-05 11:45 +1100
Message-ID<mailman.1343.1360025121.2939.python-list@python.org>
In reply to#38147
On Tue, Feb 5, 2013 at 10:16 AM, Steven D'Aprano
<steve+comp.lang.python@pearwood.info> wrote:
>     from numbers import Number
>     if isinstance(x, Number):
>         ...
>     else:
>         raise TypeError
>
>
> or Ask Forgiveness:
>
>     x + 0
>     ...
>
>
> where in both cases the ellipsis ... is the code I actually care about.

Caveat to my previous post: I would NOT indent the function body for
the sake of this check. I'd negate it:

if not isinstance(x, Number): raise TypeError

(hopefully with further information in the TypeError). I don't like
the code style that puts conditions, then more code, then error
handling - I prefer to fail-and-bail.

ChrisA

[toc] | [prev] | [next] | [standalone]


#38154

FromEthan Furman <ethan@stoneleaf.us>
Date2013-02-04 16:26 -0800
Message-ID<mailman.1344.1360025565.2939.python-list@python.org>
In reply to#38147
On 02/04/2013 03:16 PM, Steven D'Aprano wrote:
> The eternal conflict between "Look Before You Leap" and "Easier to Ask for
> Forgiveness than Permission" (LBYL vs EAFP) continues...
>
> 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
>      ...

As Ian mentioned, both cases are LYBL, unless of course your addition 
was just an example of some mathematical code you have further down.

Personally, I go with EAFP unless I'm trying to present friendlier error 
messages.

~Ethan~

[toc] | [prev] | [next] | [standalone]


#38157

FromOscar Benjamin <oscar.j.benjamin@gmail.com>
Date2013-02-05 01:00 +0000
Message-ID<mailman.1346.1360026033.2939.python-list@python.org>
In reply to#38147
On 4 February 2013 23:16, Steven D'Aprano
<steve+comp.lang.python@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

[toc] | [prev] | [next] | [standalone]


#38172

FromTerry Reedy <tjreedy@udel.edu>
Date2013-02-05 02:53 -0500
Message-ID<mailman.1357.1360050863.2939.python-list@python.org>
In reply to#38147
On 2/4/2013 6:16 PM, Steven D'Aprano wrote:
> The eternal conflict between "Look Before You Leap" and "Easier to Ask for
> Forgiveness than Permission" (LBYL vs EAFP) continues...

A somewhat different answer is that it depends on what you want the 
function to do, as documented and *tested*. And that partly depends on 
whether it is educational code for humans, production app code, or 
library code.

The test driven approach would be to write tests and then do what is 
needed to get them to pass. Doctests, unittests, and my private function 
test functions allow testing for raising a particular exception. 
AssertRaises() is used, perhaps increasingly, in stdlib tests. The 
absence of any tests for the response to 'bad' input suggests that the 
responses are 'undefined'.

That said, I admit that Python's extensible class system makes bad-input 
testing harder. I also think that anyone who uses non-builtin classes 
outside of their intended use area has to take responsibility.

For instance:

def f(n, a):
   if n < 0: raise ValueError('n cannot be negative')
   b = 0
   while n:
     b = process(a, b)
     n -= 1

looks like a safe LBYL function. But suppose n is an instance of a class 
that perversely implements subtraction as addition (or as doing 
nothing). Algorithm termination is based on the presumption that 
'decrementing' a 'positive value' moves it 'toward 0' and can only be 
done a finite number of times. Verifying that an input is a member of 
that abstract class is not trivial ;-).

 > 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.

A silent infinite loop is bad. Infinite recursion stopped with the 
recursion limit check is less bad.

If tests pass with no check, then nothing need be done until one moves 
from correctness to resource use.

-- 
Terry Jan Reedy

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.python


csiph-web