Closures basically mean that an inner function defined in an outer function remembers what its enclosing name space (or name spaces) looked like at definition time. The variables defined in this name space can later be accessed by the inner function:
def closure():
x = 1
def inner(y):
return y - x
return inner
print(closure()(5))
4
Closures also work, when an variable is passed to the outer function:
def otherClosure(x):
def inner(y):
return y - x
return inner
print(otherClosure(1)(5))
4
Before we define the decorator, let us define a simple function:
def sub(x, y):
return x - y
print(sub(5, 4))
2
print(sub(5, 4))
-1
With the closures concept in mind, we can define a decorator, which is nothing else than a function that takes another function and extends this function with some new functionality. The function to be decorated is passed to the extending inner function via the name space of the outer function. In our example, the sub function shall be modified so that it either returns x - y, or 0, if x - y would be negative.
def wrapper(func): # outer function
def zeroIfNegative(x, y): # inner function
if x - y > 0:
return func(x, y)
else:
return 0
return zeroIfNegative
The decorator is applied to the function by calling the decorator with the function as an argument:
sub = wrapper(sub)
print(sub(5, 6))
0
print(sub(5, 4))
1
As an alternative to the previously described way of applying the decorator, the @ symbol can be used:
@wrapper
def sub2(x, y):
return x - y
print(sub2(5, 6))
0
print(sub2(5, 4))
1
Decorators can have arguments. These need to be passed to the decorator via another(!!) outer function:
def wrappedWrapper(okText, nioText):
def wrapper(func):
def zeroIfNegative(x, y):
if x - y > 0:
print(okText)
return func(x, y)
else:
print(nioText)
return 0
return zeroIfNegative
return wrapper
@wrappedWrapper("Difference > 0.", "Difference < 0")
def sub3(x, y):
return x - y
# OR: sub3 = wrappedWrapper("Difference > 0.", "Difference < 0")(sub3)
print(sub3(5, 6))
Difference < 0 0
print(sub3(5, 4))
Difference > 0. 1