Script help: Holiday edge case lead to empty datasets

Oh I missed it, it is a NameError due to referencing hoursSum when it wasn't defined. You are right that slicing doesn't cause an index error.

If someone did this

some_list = []
x = [some_list[1:2] or ["not found"]][0]
# x == "not found"

I would want to strangle them lol. Sometimes letting error's bubble is the right thing to do.

I like the Phil Crosby quality system, standard for quality is zero defects.

When @lrose showed me I can avoid using the Try & Except, I switched to that.
Though I also read how Try & Except are used frequently in the development of Python itself, I still perceive using Try & Except as indicators of defects.

They're really not. I don't like them very much, but it's a clear improvement over returning error codes (look up errno for some good old fashioned fun).
I have some vivid ptsd flashes of managing errors in C...

1 Like

on error goto err

It depends on what you're doing. If for instance you're "trying" to open a file that may or may not be where you expect it, then it isn't a defect it is you handling the error and responding appropriately. However, if everything is within you're control as the programmer (i.e. some outside system or user can not change it) then write the code in a robust way that doesn't error.

2 Likes

Using try/except is in many situations is the preferred and the pythonic way to do many things.

You will get errors in your code, whether from faulty user input, or a file you're expecting to be there and it's not, etc.

One issue with asking permission is you might have something like

if os.path.exists("some/file/path"):
    doSomethingWithFile("some/file/path")

and by the time your inner function gets to doing something with that file path its been moved and you end up needing to handle the error anyways.

Compared to

try:
    doSomethingWithFile("some//file//path")
except FileNotFoundError, e:
    # Handle error
    handleFileNotFoundError(e)

So the try/except I think is more succinct here.

Alternatively, with asking for permission first, you always do it, where as asking for forgiveness, you only need to run your except clause in situations where things went wrong, so there is a (slight) performance boost (though I don't do it for that).

One caveat here is you MUST use the appropriate error in each catch or it will become useless and more confusing than helpful.

2 Likes

That is really intricate.

So at any point if the try fails, then the except happens.

Using the permission clause, can be more complicated or simpler.

To use the file manipulation example, imagine that you would have to check if the file exists, if it's the right format, if you're allowed to open it, to write to it, etc... Using ifs and error codes as you would do in C, it quickly becomes a nightmare.
With exceptions ? You try to do what you have to do, then catch the error if one happens, with different handling for different errors. It's clear and simple.
I can pm you a link to some things I've written in C if you want to see how much of the code can be dedicated to error handling.

I thought I'd address a few things.

  • ZipExhausted
    It is defined in the code:
class ZipExhausted(Exception):
    pass

So, it's a class that inherits from Exception. Which means it can be used as an exception.
pass means that this class doesn't define anything by itself, it's really a standard exception, with a different name. Why a different name ? So that you can catch it specifically.

  • raise
    This simply raise an exception. You raise your own exceptions, so that they can be handled somewhere else. It's basically a "return error".

  • yield
    This one is a bit more complicated. It has to do with generators.
    Simply put, it's a return that keeps track of where the function's execution was when it returned.
    Let's say you have this function:

def get_numbers():
    yield 1
    yield 2
    yield 3

Now, when you call this function, it will return a generator that can produce the numbers 1, 2 and 3.
ie:

for n in get_numbers():
    print n

will print

1
2
3

sum(foo())

will return 6.
etc.

  • repeat()
    This produces a repeat object, which is basically a generator that repeats something.
for x in repeat('foo', 3):
    print x

will print

foo
foo
foo

  • chain()
    That simply chains things in a sequence. It sorts of 'flattens' it.
list(chain([1, 2], [3, 4], [5, 6]))

returns [1, 2, 3, 4, 5, 6]. I used list because chain itself returns, again, a generator (or, more accurately, a chain object, but, just like repeat objects, you can use it like a generator)

2 Likes

Did you mean sum(get_numbers()) rather than sum(foo())?

And thank you.

If you link the C code, and think seeing it will help me then I will definitely look at it.

Yes indeed, I meant get_number().
The C code wouldn't help you much, unless you plan on implementing compilers, VMs or weighted path finding...
I was merely suggesting that the comparison with C style error handling would show how convenient try/catch can be.

1 Like