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)
# 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()
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."
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')
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()
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
@double_function def return_20(): return 10 print return_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)
-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