I really don't like this example of yours - I hope it's not your introduction to wrappers. It's a case that is more complex than is usual, because the wrapper takes arguments so there's an extra layer of indirection. Let's try a simpler example.
Let's say I wanted to make a wrapper that catches any exceptions in a function and prints the error. Let's called it "printexc"
Code:
def printexc(f):
def wrapper(*args, **kwargs):
try:
return f(*args, **kwargs)
except Exception as e:
print("An error occurred: {}".format(e))
return wrapper
@printexc
def test1():
return 1
@printexc
def test2():
raise Exception("foo")
print("test1")
test1()
print()
print("test2")
test2()
This outputs
Code:
test1
test2
An error occurred: foo
See what's happening here? Wrapping test1 with printexc is the same as saying
test1 = printexc(test1)
printexc() is a function that returns the function "wrapper" right now. The code in "wrapper" doesn't run until you try to invoke test1(). When you do, it runs wrapper instead.
wrapper needs to return f(*args, **kwargs) because otherwise whatever has called test1 won't get it's return value.
If you had another wrapper called, like, printexc2 and you used both, like
Code:
@printexc
@prntexc2
def foo():
... do somethnig
Then this is the equivalent of
Code:
foo = printexc(printexc2(foo))
So when you invoke foo, its going to call the wrapper() returned by printexc, then it's going to call the wrapper() returned by printexc2, which is then going to call foo.
It's layered, so each layer has to call the function it wraps, and return the return value back up the stack.
Last edited by RustyBrooks; 05-13-2018 at 03:23 PM.