-1

I'v constructed these two bits of code and I wonder which is 'better'. By that I don't necessarily mean which one is faster to execute since that isn't a concern for my program. Which one is more readable?

Either:

A = 1
B = some_other_value
try:
    A /= B
except ZeroDivisionError:
    pass

Or:

A = 1
B = some_other_value
if B != 0:
    A /= B
MarcinKonowalczyk
  • 2,577
  • 4
  • 20
  • 26

3 Answers3

2

The second may be more readable, but the first allows for greater polymorphism and duck typing (in the case that B is not an float or int, but an object that supports division).

Most of the discussion here seems to be missing the point that B could be a custom object that supports division. Consider the below exapmles with a NumType class acting as the denominator.

>>> class NumType(object):  # basically, an int wrapper
...     """Custom number type.  Supports division when it is the denominator"""
...     def __init__(self, val):              
...             self.val = val
...     def __rdiv__(self, num):
...             return num / self.val
... 
>>> c = NumType(1)  #normal division works as expected
>>> 5 / c
5
>>> c = NumType(7)
>>> 5. / c
0.7142857142857143
>>> c = NumType(0)
>>> 5 / c  # 0 division causes exception
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in __rdiv__
ZeroDivisionError: integer division or modulo by zero
>>> # pay attention here:
>>> if c != 0:  # condition is True because c is an object, not 0
...     print 5 / c  # so this division still occurs, and generates an exception
... 
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 5, in __rdiv__
ZeroDivisionError: integer division or modulo by zero
>>> # however, this is caught with a try/except
>>> try:
...     5 / c
... except ZeroDivisionError:
...     print '0 division'
... 
0 division
Ryan Haining
  • 35,360
  • 15
  • 114
  • 174
1

Of course the second one:

A = 1
B = some_other_value
if B != 0:
    A /= B

Not because it's more readable but because you're not using exceptions for a non exceptional condition. If an invalid input or an input that may cause an error is possible in normal circumstances then you should never use exception to handle it.

You can find very good explanations about this around the web but in my opinion (beside performance) is always because of intent: when you handle an exception then you make clear to readers that it's an exceptional condition, an error, something that should not happen in normal program flow. In your case it's not because an invalid input is absolutely possible (every single input must be validated, always).

From my point of view the fact that you want to handle it means that's it's possible then a check is always the right solution. Like Ryan said, you may have higher polymorphism if you don't have to check for zero, then what you have to do is to move the check where you can handle the situation (in the / operator for example and not where you use it).

EDIT
Few words more to summarize what I wrote as comments. Python encourage a programming style called EAFP, this is a great resource when you write small scripts but it should be used carefully when you write applications and libraries.

It helps to keep code short (and quick for the most common code path) but it has a big disadvantage: if you don't wrap in try/execpt each couple of lines you'll have to handle intermediate results and partials computations. A big try may leave application in an undeterminate state (or it'll just make your code much less readable because a long list of - possibly nested - try/except/else/finally).

I agree it helps to use your code in situations you didn't even think about but this is not flexibility, it's fragility. It may be OK for a short personal script but if I write an application (or, even more, a library) I want to be sure where and how my code will fail, I'll write tests to check how it works with different inputs. I don't want it'll fail in ways I can't predict, unexpected input > unexpected behavior sounds too much like garbage input > garbage output. Application must be robust. After you checked everything you can check then you even have to be ready for exceptional situations (I may relax this rule for so rare situations that checking may be paranoic).

Zulu
  • 8,765
  • 9
  • 49
  • 56
Adriano Repetti
  • 65,416
  • 20
  • 137
  • 208
  • division by zero isn't exceptional ? – Hunter McMillen Jul 01 '13 at 16:12
  • @HunterMcMillen **no, it's not** because inputs must always be **validated**, especially if you suppose it may happen (and the empty except block let me think they must be). – Adriano Repetti Jul 01 '13 at 16:17
  • 2
    @Adriano EAFP is more pythonic, input validation is generally discouraged. And you'd need type testing on top of this to actually validate the inputs, which is rarely encouraged. – Ryan Haining Jul 01 '13 at 16:25
  • @Adriano Thank you. This is exactly the answer I needed. – MarcinKonowalczyk Jul 01 '13 at 16:51
  • @447xpro input validation is not the right way to code in python. `if B != 0` could pass and the division itself then still fail on a `ZeroDivisionError` – Ryan Haining Jul 01 '13 at 16:57
  • @RyanHaining ... I see. That's a good point. In my particular case though the code within 'if'/'try' is very short and simple so its not a problem. I think I'll use 'if' since its two lines shorter. – MarcinKonowalczyk Jul 01 '13 at 17:06
  • @447xpro doing things with duck-typing in mind is more to allow your code to be used in the future in ways you didn't imagine in the first place. If you're writing some one-off script then it's not a big deal. If you're writitng anything substantial though, you should make a habit of this kind of coding. Also read up on "Easier to Ask Forgiveness than Permission" – Ryan Haining Jul 01 '13 at 17:12
  • @RyanHaining "...input validation is not the right way to code in python...". OMG It's Always the right way to code in every single Language we have on this world...when you don't...it means you'll do it in another place, not that you won't do it at all! – Adriano Repetti Jul 01 '13 at 17:16
  • @Adriano you're not suppose to be testing the values of your inputs or checking their types or whatever in order to avoid an exception, just do what you're trying to do and handle the exception if it occurs. If you consider `try`/`except` to be input validation, then fine I guess, but I'd tend to think that input validation is checking you inputs before trying to do anything with them. – Ryan Haining Jul 01 '13 at 17:25
  • @RyanHaining I can't agree because in **anything longer than five lines** you have **intermediate results** and you compute **multiple values** (unless you want to put try/except for each couple of lines). An exception will leave everything in an **undeterminate state** (then you may not be able to **handle** the exception in the proper way). It can be OK for a small function or an utility script but an application must be **reliable**. – Adriano Repetti Jul 01 '13 at 17:49
  • Inputs muust be validated where you can but you shouldn't avoid it, just do it where you have enough information to handle it. You may pay a little price in flexibility but you gain a lot in security and robustness (because whatever will happen your application will **fail in a controlled way** or it won't fail at all). – Adriano Repetti Jul 01 '13 at 17:52
  • Python is a great and expressive Language but if used with too much freedom will make your **applications** an undeterministic messy of code that may fail at any moment (this is true for any Language). – Adriano Repetti Jul 01 '13 at 17:53
  • @Adriano if reliability is your concern, then why would you disagree with a `try`/`except`, knowing that the `if` test will pass values that will cause a `ZeroDivisionError`? – Ryan Haining Jul 01 '13 at 17:54
  • @Adriano the `try`/`except` is more robust – Ryan Haining Jul 01 '13 at 17:55
  • @RyanHaining validation doesn't mean you don't have to be ready for exceptions! Check what you can check, fail WHEN and WHERE you want (and not when/where inputs you don't control may _decide_) and then catch EXCEPTIONAL situations. If I **write, document and TEST** a function that handles numbers... **I don't want it works with somehting I DO NOT KNOW** because I never tested it with that inputs. It may fail, it may not, it may works for 99% of inputs and fail next year. It's good for a short script for personal use, it's not good for an application you sell. **Fragility is not flexibility**. – Adriano Repetti Jul 01 '13 at 18:08
  • @Adriano how is it more fragile to except rather than do the if test? If you `except` the `ZeroDivisionError` you are less fragile and more flexible than if you do the `if`. There's no reason to intentionally limit the scope of your function. Yeah, if you want to, you can document saying "This only works with numbers, don't use anything other than built-in, numeric types" but why would you want to do that? If someone wants to use your function with their own numeric, then they should test to see if it works. You're just limiting what your function can handle. – Ryan Haining Jul 01 '13 at 19:07
  • I can overload __ne__ and make your function compare my custom classes however I want, if I know you're using an if. If you really want to make this comprehensive, then you should be doing `if isinstance(B, (int, long, float)) and B != 0:`. – Ryan Haining Jul 01 '13 at 19:11
  • @RyanHaining Exactly! When you write a function you create a **contract** with its users (about parameters, rules, output). Your function should never fail in an uncontrolled way and if someone wants to use it with a **new numeric type**...it's their **duty to conform to that contract**. I have to repeat myself: this is true for any real world medium size application, if you're writing a small script to automate something...probably you'd better use the other style (this is where Python is so good, you choose the **coding style that better suits what you're doing**). – Adriano Repetti Jul 01 '13 at 19:50
  • (Anyway...if you're writing a new numeric type then you have to implements implicit conversions to standard numeric types too or your class won't integrate with anything else. In this case the check won't be broken and everything will integrate smoothly). Of course if you're planning to use a function that calculate an average (for example) to sum/divide a custom class of strings...well then I wouldn't like that my function accepts that input and I would force you to do an explicit expansive clear conversion to make your intent obvious (and not implicit). – Adriano Repetti Jul 01 '13 at 19:58
  • @Adriano so then isn't the real answer to just state that the function doesn't accept any non-zero value for the denominator? And as far as creating a contract goes, it's less to ask to just state that the argument must support integer division than supporting integer division and comparision with an integer, since the former is all you need for your purposes. – Ryan Haining Jul 01 '13 at 20:54
  • @RyanHaining well, if you (can) ask "give me two numbers, if you give garbage then I'll throw an exception and I'll leave object in an undeterminate state" then yes, do it. In this case you don't even need to catch the exception. BUT if you want to handle that situation then it's better to do it in the proper way (with a more strict contract, if needed). Would you ever put random oil/gasoline in your car? No because you may break something (now, tomorrow, after one year). A program can handle this _error_ (and it's its duty to do it). – Adriano Repetti Jul 01 '13 at 22:32
  • (in reality you may even need to be **more strict**. what about **0/0**? a good routine/class made to interop well should rely on exceptions only for things it can't control, last safe point for unpredictable star conjuctions...) – Adriano Repetti Jul 01 '13 at 22:36
  • To go back to car example: even if you MAY do something it doesn't mean you SHOULD do it. Software may control things that MUST NOT FAIL. It means that you must always know what's going on, where you are and why. NASA had to learn a strong lesson about software integration. – Adriano Repetti Jul 01 '13 at 22:39
  • @Adriano I wouldn't put random gasoline into my car, but if someone invenvted a biofuel that toytoa hadn't foreseen, but was safe, then why wouldn't I? Unless the car manufacturers went through the trouble of analyzing the chemical properties of whatever gas I put the car, and had the car refuse to run if it didn't know exactly what was in it. I don't see how an `if` test gives you a more proper way to address something than a `try`/`except` would, can you explain what you mean by that? – Ryan Haining Jul 01 '13 at 22:47
  • @RyanHaining They don't analyze every single gas you may use but they give you a specification. If you use something else...it may work, it may not...it's your business and they don't care. For software is the same, to assert your function works you have to put restrictions and checks. Usually this is more important than (blind) flexibility because if one airplane falls noone will feel less sorry 'cause a programmer saved 100 lines of code. – Adriano Repetti Jul 01 '13 at 22:53
  • I don't say IF is better than TRY/EXCEPT. But to have the same granularity you may need to wrap few lines of code for each try/except. Of course it'll work (and noone will stop you to do this) but MAYBE it'll hurt readibility a lot. Back to question: if to handle 0/0 you have to do that check inside the except... – Adriano Repetti Jul 01 '13 at 22:57
  • @Adriano You're overspecifying though, you don't require comparison with integers to be successful, all you need is something that properly acts as a denominator. There's no reason to require more from the contract. and 0/0 is the same problem in either, what's the difference? – Ryan Haining Jul 01 '13 at 23:21
  • @RyanHaining no, I would restrict inputs to exactly what I need to assert my function works as expected. Nothing more and nothing less. If this implies I need a comparison with an integer...well, I (happily) pay price for that. 0/0 must (should) be handled different because 0/0 != 1/0 (how to deal with it, well it depends what you're doing, a fictional example like that is too academic). – Adriano Repetti Jul 02 '13 at 07:26
  • @Adriano are you promoting type testing all function arguments then? – Ryan Haining Jul 02 '13 at 16:25
  • @RyanHaining of course not! Only arguments you want to handle in some special way (in the same way you do not wrap each function with try/except). In medio stat virtus, I'm promoting to _validate_ parameters (almost always for any public function), _restrict usage_ (_when it makes sense_ in favor of robustness), _test before usage_ (every time you can unless the test is too time consuming), _catch everything else_ (only _if you can do something_ with that exception otherwise is pretty useless). – Adriano Repetti Jul 02 '13 at 17:51
  • @Adriano so if I say "give me a non-zero value for the denominator" in the docstring of my function, then I shouldn't have to check for zero values. So if I say "give me something that can act as a denominator with an int literal and is non-zero" I shouldn't have to do any checking at all. I still fail to see how a try/except is fragile compared to an if. There's no advantage to the if at all besides being 2 lines shorter. You shouln't type test because it's slow and limitting. All you need is a denominator to work as expected, nothing more, nothing less. – Ryan Haining Jul 02 '13 at 20:44
  • @Adriano how about this. You have a function that includes the line `for element in iterable: do_something(element)` where iterable is a parameter. It's unreasonable to test this with every generator/iterable builtin, much less user defined ones. So do you start your function with `if not isinstance(iterable, (list, tuple)): raise TypeError('function only accepts lists and tuples')` to throw out everything you haven't explicitly tested? Of course not! If that's how python coders did things, the libraries would all be crippled. – Ryan Haining Jul 03 '13 at 16:50
  • @RyanHaining 1st msg: I agree with first sentences, if you make it clear in doc you may even skip checks (well, it's not always acceptable especially when you can handle that error but it's too academic): users will know it fails in that conditions. – Adriano Repetti Jul 03 '13 at 18:24
  • I don't say try/except is fragile compared to an if. I say that if you want same fine control (again because of intermediate results and objects state) you have to put tons of try/except (and it'll make your code hard to read because you'll have more error Handling than logic). On the other side if you don't do it (and you rely on documentation only) then your function may fail in unexpected ways. – Adriano Repetti Jul 03 '13 at 18:25
  • @RyanHaining 2nd msg: of course not! **To validate parameters doesn't mean that you have to check what's obvious** (or what will may throw an exception by itself because of usage). If you do something like that you raise (more or less) the same error would be thrown _naturally_ so you don't add meaningful information (see my comment for 1st msg). – Adriano Repetti Jul 03 '13 at 18:29
  • Take a look to lib\ files, most of except are for pass (and each time a check of parameters will add some information they'll do it). Even if a programming style is possible (try/except) it doesn't mean you have to use it everywhere. [Random picking from Python Standard Library](http://hg.python.org/cpython/file/8e838598eed1/Lib/imaplib.py): inputs are validated when needed, checks are made in advance, except is used to pass or when to take a proper action is possible (even ignore an error may be a proper action). What it means? Don't restrict usage but don't refrain to do it if you'll need it – Adriano Repetti Jul 03 '13 at 18:43
  • What's good for a short script isn't necessary good for a library. This doesn't mean I'll validate everything in every script I write but yes I'll validate inputs (**values, not types** unless it's strictly required) and I'll check in advance in every single script I'll write to be used by someone else and in every single script I'll write in production applications. – Adriano Repetti Jul 03 '13 at 18:44
  • @Adriano Can you point to what lines in that link have input validation? and **don't restrict usage but don't refrain to do it if you'll need it** -- well, you **don't** need it, your checks are making you need it. You're restricting usage where you don't need to. – Ryan Haining Jul 03 '13 at 20:55
  • @RyanHaining I suppose we may go on like this for years! Could you show me an example (or a link) of where you wouldn't not validate inputs? (validate = check VALUES, not TYPES unless really required). Or a real world example of where you catch an exception you may prevent with a check? – Adriano Repetti Jul 03 '13 at 21:21
  • Validation? 230, for example. Or starttls. Whenever possible (so if something bad will happen YOU won't see an error message from an obscure unknown routine).But we're missing the point...to check inputs is first basic step to write a reliable program (do you remember "garbage in>garbage out"?). It's not taste or style, it's mandatory for each serious > 20 LOC program BUT it doesn't mean you won't use try/except! Unvalidated input may not even do not throw, just produce garbage. – Adriano Repetti Jul 03 '13 at 21:33
  • Back to our question what I mean is: if I can HANDLE an error condition then I check; if I can provide an informative message then I check before; if I have to write tons of try/except to keep a good granularity...then I check. When you would avoid? In the link I posted I can't see even one example of a try/except that can be avoided (so **use it, it's pythonic**) but **don't abuse it**. – Adriano Repetti Jul 03 '13 at 21:36
  • @Adriano I'll give you that never validating inputs is an overstatement. But 230 is a weird situation since `__getattr__` is effectively handling an `AttributeError` already. I'm not sure what you're talking about in `starttls` cause the only check I see is `if ssl_context is None` which is a check for whether the argument was passed at all. For an exapmle of try/excepting over a check: on `1403` they `except AttributeError` rather than checking first with `hasattr`. – Ryan Haining Jul 03 '13 at 22:17
  • My point from the start here is that if he really wants to avoid division by zero, he should except any `ZeroDivisionError` that occurs because it's not just easier to ask forgiveness than permission, it's also more accurate. – Ryan Haining Jul 03 '13 at 22:18
  • If you know what problem you're trying to prevent, it's more thorough to `except` the associated exception than to try to exhaustively check for all conditions that could cause it. It's very possible you'll miss one in a complex situation. – Ryan Haining Jul 03 '13 at 22:20
  • @RyanHaining exactly in Time2Internaldate they even check the input type with isinstance (would you rewrite to try/except?), it's just an _optimization_ for a common code path. Few lines before in ParseFlags they don't. – Adriano Repetti Jul 03 '13 at 22:41
  • In which case it may throw ZeroDivisionException after the check? If it does then it's a problem in the input class implementation and not a situation we should handle. I guess it's _less accurate_ because it hides an error the should come out. – Adriano Repetti Jul 03 '13 at 22:43
  • Of course I wouldn't check (MAYBE) every condition, only if I can add meaningful bla bla bla. Moreover test doesn't mean you won't except (but with coarse granularity) – Adriano Repetti Jul 03 '13 at 22:44
  • (by the way...this is a good example of questions that have to be closed because of "...as primarily opinion-based..." LOL) – Adriano Repetti Jul 03 '13 at 22:48
  • @Adriano I believe the `isinstance` to which you are referring is there because it handles different types in different ways, I'm referring to the try/except they have in place of `if hasattr(...)`. The same example with an object that has an `__rdiv__` method. If your goal is to handle/detect the case where the numerator is divided by zero, the most complete way to detect that case is to `except ZeroDivisionError` regardless of whether you blame the host class for not implementing a comparison operator for `int` which it may have a good reason for not doing. – Ryan Haining Jul 03 '13 at 23:13
0

The answer is: it depends.

There is the consideration of whether you can easily write the appropriate test to handle a condition. In this case, you can.

The other consideration is whether your handwritten tests are sufficient. In a single line of code this is easy. If you had a number of operations, it might make more sense to assume that everything is fine, and figure it out afterwards with an exception. (Of course, in such a case, input validation might in general be more useful).

As with most things in programming, this is a matter of style and taste. That is particularly true in python, which generally has multiple ways of doing things (notwithstanding any easter eggs you may have read), and which is largely premised on the idea that programmers are able to make their own decisions (unlike, e.g. Java, which does really try to enforce a single style of programming, by making everything unexpected harder and more unpleasant than the anticipated technique).

Think about what you're doing and why. Try not to repeat yourself.

Marcin
  • 48,559
  • 18
  • 128
  • 201