## 2014-10-29

### What is a Python decorator?

Suppose we have a function that returns the boiling point of water in celsius:
```def boiling_point():
return 100

print boiling_point() # returns 100
```
Suppose we are now at high altitude and the boiling point is now 97 degree. We can wrap the function like this:
```def calibrate(f):
def g():
return f() - 3
return g

def boiling_point():
return 100

print boiling_point() # returns 100

boiling_point_calibrated = calibrate(boiling_point)
print boiling_point_calibrated() # returns 97
```
In fact, we can do better. We can assign the calibrated function back to the original function name. As a result, nothing is changed on the calling side:
```def calibrate(f):
def g():
return f() - 3
return g

def boiling_point():
return 100

print boiling_point() # return 100

boiling_point = calibrate(boiling_point)
print boiling_point() # returns 97
```
When used in this way, the function calibrate() is called a decorator, and there is a shortcut for it:
```def calibrate(f):
def g():
return f() - 3
return g

@calibrate
def boiling_point():
return 100

print boiling_point() # returns 97
```
Even better is for the offset to be configurable. The boiling point is dependent on the altitude after all. What we can do is to create a function which returns a function that decorates a function:
```def calibrate_offset(offset):
def calibrate(f):
def g():
return f() - offset
return g
return calibrate

@calibrate_offset(3)
def boiling_point():
return 100

print boiling_point() # returns 97
```
Finally we can generalize the original function by adding *args and **kwargs to it:
```def calibrate_offset(offset):
def calibrate(f):
def g(*args, **kwargs):
return f(*args, **kwargs) - offset
return g
return calibrate

@calibrate_offset(3)
def boiling_point():
return 100

print boiling_point() # returns 97
```
Lastly, decorator can be nested. What do you think x will be in the following case?
```def calibrate_offset(offset):
def calibrate(f):
def g(*args, **kwargs):
return f(*args, **kwargs) - offset
return g
return calibrate

@calibrate_offset(3)
@calibrate_offset(4)
def boiling_point():
return 100

x = boiling_point()
```