1

I have a decorator:

def remediation_decorator(dec_mthd):
    def new_func(*args, **kwargs):
        try:
            return dec_mthd(*args, **kwargs)
        except (KeyError, HTTPError) as err:
            print(f'error = {err}... call the remediation function')
    return new_func

Inside the generator function, another function is called to raise specific exceptions under certain conditions:

def check(number):
    if number == 1:
        raise HTTPError
    if number == 2:
        raise KeyError

This generator function is decorated like so:

@remediation_decorator
def dec_mthd_b(number):
    check(number)
    for i in range(0,3):
        yield i+1

When an exception is raised by the check function, the decorator's except is not hit.

[ins] In [16]: dec_mthd_b(1)
Out[16]: <generator object dec_mthd_b at 0x10e79cc80>

It appears to behave like this because it's a generator function - from Yield expressions:

When a generator function is called, it returns an iterator known as a generator.

(I wonder whether to take this in the literal sense 'it returns the iterator first irrespective of other logic in the function', hence why check() does not raise the exception?)

and,

By suspended, we mean that all local state is retained, including the current bindings of local variables, the instruction pointer, the internal evaluation stack, and the state of any exception handling.

Have I understood this correctly? Please can anyone explain this further?

hitzoglin
  • 83
  • 1
  • 6
  • I don't know the answer unfortunately but I have bookmarked [this question](https://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do) for myself as I always forget how `yield` and generators work. Maybe it will be of some use. – havingaball Dec 02 '21 at 14:50
  • There is an excellent overview [here](https://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do). Reading this through again, I think it gives a pretty good explanation of what is happening here as it says 'when you call the function, the code you have written in the function body does not run' – hitzoglin Dec 02 '21 at 14:59
  • next(dec_mthd_b(1)) raises the desired exception – jwzinserl Dec 02 '21 at 18:15

2 Answers2

0

You have understood it correctly. The below code will throw an exception. When a generator is created nothing gets executed. You need to fetch the next element, hence get the value from generator, then it will raise the exception.

g = dec_mthd_b(1)
next(g) #throws httperror

In fact thats how iteration is done, we repeatadly call next method, until IterationError exception is thrown.

Albin Paul
  • 3,330
  • 2
  • 14
  • 30
0

Yes you got it. @remediation_decorator is a Syntactic Sugar in python for decorators. I'm going to use the verbose(?) form:

def dec_mthd_b(number):
    check(number)
    for i in range(0, 3):
        yield i + 1

dec_mthd_b = remediation_decorator(dec_mthd_b)

What does this line do ? remediation_decorator is your decorator, it gives you the inner function, in your case new_func.

What is new_func ? It is a normal function, when you call it, it runs the body of the function.

What will return from new_func ? dec_mthd(*args, **kwargs).

Here dec_mthd points to dec_mthd_b and it is a function again. But when you call it, since dec_mthd_b has yield` keyword inside, it gives you back the generator object.

Now here is the point. The body of your inner function, here new_func, is executed without any problem. You got your generator object back. No error is raised...

# this is the result of calling inner function, which gives you the generator object
gen = dec_mthd_b(1)

# Here is where you're going to face the exceptions.
for i in gen:
    print(i)

What will happen in the for loop ? Python runs the body of the dec_mthd_b. The error is raised from there...

So in order to catch the exceptions, you have two options, either catch it inside the dec_mthd_b, or in the last for loop.

S.B
  • 13,077
  • 10
  • 22
  • 49