A brief introduction to Functional Programming in Python.
Popular functional programming languages:
I do not claim to be a master of functional programming nor is this meant to be a lesson on functional programming. This is meant to be a look into Python features which are related to or insipred by functional programming. While these concepts are handy for all Python programmers and will carry into other functional languages, this is not meant to imply that Python is a great functional language.
You’ll notice Python is not listed as a popular functional langages. Python has functional tools and idioms but is not a purely functional language. If you find think interesting I suggest you consider looking into one of the previously listed functional languages.
Testing and debugging functional programs tend to be easier. There is no global state and the program is a sequence of function calls. Functions tend to be small and compartmentalized. Each function can be tested individually. Debugging involves checking the input and output of each function call.
Functional programming/languages have been growing in popularity in industry and knowing these languages/concepts makes for a rare marketable skill.
Also it’s fun.
There was a mention about not passing an empty list as a default argument. This is not only true of lists but any mutable type such as a set or a dictionary.
This is because the function defaults are evaluated when the function is defined. If the default type is mutable then it can be changed on subsequent function calls.
def simple(x, y=[]):
y.append(x)
return y
print simple.func_defaults
print simple(1)
print simple(2)
print simple.func_defaults
Small logical blocks can often be removed by making use of Python short circuiting logical statements.
x = 2
x_place = 'did not place'
if x == 1:
x_place = 'first'
elif x == 2:
x_place = 'second'
print x_place
x = 2
print (x == 1 and 'first') or (x == 2 and 'second') or 'did not place'
One of the best uses of this logical evaluation is handling default arguments that you want to be mutable types such as lists or dictionaries.
def simple(x=None):
x = x or {}
# Do some more work
List comprehensions allow you to build loops in place. Simple loops can be removed with list comprehensions. The Python 3.X series supports dictionary and set comprehensions as well.
mix = [1, 'a', 'b', 2, 4]
letters = []
for x in mix:
if isinstance(mix, basestring):
letters.append(x)
print letters
print [x for x in mix if isinstance(x, basestring)]
You can have nested list comprehensions to eliminate nested for loops.
def identity(n):
return [[1 if i == j else 0 for i in xrange(n)] for j in xrange(n)]
print identity(3)
print [(i, j) for i in xrange(3) for j in xrange(3)]
So far we have always defined functions using the def keyword. You can also define anonymous (unnamed) functions with lambda.
f = lambda x: x*x # Create a new function and assign to f
print f.__class__
print f(2)
You can also use the filter function. The first argument is the function to be used for the filtering. This could be a defined function or a lambda. The second argument is the iterable. It returns the filtered list.
mix = [1, 'a', 'b', 2, 4]
letters = filter(lambda x: isinstance(x, basestring), mix)
print letters
List filtering isn’t the only type of lists you can remove. The reduce function applies a binary function in sequence to an iterable.
Here is a simple total using reduce.
# This is equivalent to ((1 + 2) + 3) + 4)
print reduce(lambda x, y: x + y, [1, 2, 3, 4])
# This is equivalent to ((1 * 2) * 3) * 4) or 4!
print reduce(lambda x, y: x * y, [1, 2, 3, 4])
reduce is built-in to Python 2.X but was moved to the functools module in Python 3.X.
map applies a function to each item in an iterable and returns a list of the results.
# Create list of the first four squares
squares = []
for x in [1, 2, 3, 4]:
squares.append(x**2)
# Becomes
squares = map(lambda x: x**2, [1, 2, 3, 4])
text = '1,0,0;0,1,0;0,0,1'
rows = text.split(';')
matrix = []
for row in rows:
matrix.append([float(x) for x in row.split(',')])
print matrix
matrix = map(lambda x: map(float, x.split(',')), text.split(';'))
print matrix
The built-in list type has a sort method which sorts the list in place. sort can also take a key parameter which is a function which takes one parameter returns the value which will be used for the sorting. Passing reverse=True will reverse the sort order.
test = [3, 4, 5, 1, 2]
test.sort()
print test
test = [3, 4, 5, 1, 2]
test.sort(key=lambda x: x % 3)
print test
test = ['C', 'a', 'd', 'B', 'E']
test.sort()
print test # Default alpha sort
test.sort(key=str.lower)
print test # Case insensitive alpha sort
test = [(1, 2), (5, 3), (7, 1), (3, 8)]
test.sort(key=lambda x: x[1])
print test # Sort by second entry
list.sort is great but what if you need to sort things that are iterables but not lists. For that you can use the built-in sorted function. The first argument is an iterable and the remaining arguments are the same as sort. Unlike sort which returns None sorted returns the sorted items as a list.
print sorted((8, 6, 7, 5, 3, 0, 9))
print sorted('jenny')
print sorted({'got': 'your number', 'need': 'make you mine'})
There are some common key functions:
While it isn’t difficult to write these functions you can instead use the operator module.
from operator import itemgetter, attrgetter, methodcaller
test = [(1, 2), (5, 3), (7, 1), (3, 8)]
print sorted(test, key=itemgetter(1))
test = ['C', 'a', 'd', 'B', 'E']
print sorted(test, key=methodcaller('lower'))
test = [1 + 1j, 2 - 1j, -1 + 2j]
print sorted(test, key=attrgetter('real'))
The itertools module defines functions for commonly used iterators.
from itertools import chain, takewhile, dropwhile
from itertools import combinations, permutations
a = [1, 2, 3]
b = 'abc'
print [(x, type(x)) for x in chain(a, b)]
print [u''.join(x) for x in combinations(b, 2)]
print [u''.join(x) for x in permutations(b, 2)]
print list(takewhile(lambda x: x % 2 == 1, a))
print list(dropwhile(lambda x: x in 'aeiou', b))
Closures are combination of a function and an environment. They are named closures because they are said to “close over” free variables that are passed when defining the closure.
Since I’m sure none of that made any sense let’s look at some examples.
def greater_than_bound(bound):
def greater(x):
return x > bound
return greater
greater_than_ten = greater_than_bound(10)
print greater_than_ten(20)
Why in the world would you want to do this? Somethings are just too complicated for lambda. lambda expressions cannot contain if, for or while statements. Though, as we have seen, many of these statements can be replaced.
A tour of the Python standard library.