from IPython.core.display import Image
Image(url='http://labrosa.ee.columbia.edu/crucialpython/logo.png', width=600)
This session of crucial python borrows heavily from http://simeonfranklin.com/blog/2012/jul/1/python-decorators-in-12-steps/ which covers decorators from the ground up.
In Python, functions are objects that you can pass around and manipulate just as you would a normal variable.
def say_hey():
""" A function which always returns the string 'hey' """
return 'hey'
def print_function_output(function):
""" A function which calls an input function and prints its output """
print function()
print_function_output(say_hey)
hey
# Functions can also be returned just like normal variables
def i_return_a_function():
""" A function which builds a new function and returns it """
def i_get_returned():
""" This is a nested function, which gets built during i_return_a_function """
print "I was built in i_return_a_function"
return i_get_returned
result = i_return_a_function()
print result
result()
<function i_get_returned at 0x10461cc08> I was built in i_return_a_function
When you define a function within another function, it will remember what the local namespace looked like at definition time.
def make_printer(print_me):
""" Construct a function which prints whatever was input to make_printer """
def printer():
""" Print the value of print_me passed to make_printer when printer was created """
print print_me
return printer
hey_printer = make_printer('hey')
you_printer = make_printer('you')
hey_printer()
you_printer()
hey you
Aside: functools.partial
is a more useful example. From the documentation: "The partial() is used for partial function application which “freezes” some portion of a function’s arguments and/or keywords resulting in a new object with a simplified signature."
print int.__doc__
int(x=0) -> int or long int(x, base=10) -> int or long Convert a number or string to an integer, or return 0 if no arguments are given. If x is floating point, the conversion truncates towards zero. If x is outside the integer range, the function returns a long instead. If x is not a number or if base is given, then x must be a string or Unicode object representing an integer literal in the given base. The literal can be preceded by '+' or '-' and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal. >>> int('0b100', base=0) 4
import functools
basetwo = functools.partial(int, base=2)
basetwo('0110')
6
Decorators are simply functions which take a function as input and return a function.
def double_function(function):
""" Return a version of function which doubles its output """
def doubler():
return function()*2
return doubler
def return_10():
return 10
return_double_10 = double_function(return_10)
print return_10()
print return_double_10()
10 20
The syntax function = decorator(function)
ends up popping up a lot when you start using decorators. So, as a convenience, Python includes the @ syntax. Placing @decorator above a function definition replaces the function at definition time with its decorated version. You might remember seeing this syntax last week; it's used by flask
extensively.
@double_function
def return_20():
return 10
print return_20()
20
Say we have a bunch of functions, all of which take at least one input: x
, which should be a float which is greater than 0. We can use a decorator to check for appropriate values of x
and apply it to each function
import warnings
def validate_x(x_function):
""" Validates the input values of a function. """
# The *args and **kwargs variables are any function arguments beyond the first
def x_function_validated(x, *args, **kwargs):
# Check that x is a float, and try casting it
if not type(x) is float:
warnings.warn('x is not a float')
try:
x = float(x)
except:
raise TypeError('Could not cast x to a float')
# Confirm that x is greater than 0
if not x > 0:
raise TypeError('x should be greater than 0')
return x_function(x, *args, **kwargs)
return x_function_validated
@validate_x
def root(x, n):
""" Compute the n'th root of x """
return x**(1/float(n))
@validate_x
def invert_multiply_add(x, multiply=1., add=0.):
""" Computes multiply/x + add """
return multiply/x + add
print root(16, 4)
2.0
-c:8: UserWarning: x is not a float
print invert_multiply_add(0, 1)
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-11-563bacac26d9> in <module>() ----> 1 print invert_multiply_add(0, 1) <ipython-input-9-e090e6c8875d> in x_function_validated(x, *args, **kwargs) 13 # Confirm that x is greater than 0 14 if not x > 0: ---> 15 raise TypeError('x should be greater than 0') 16 return x_function(x, *args, **kwargs) 17 return x_function_validated TypeError: x should be greater than 0
More substantial examples are available here: https://wiki.python.org/moin/PythonDecoratorLibrary