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()

No comments: