Functions¶
In [1]:
%load_ext rpy2.ipython
Arguments¶
In [2]:
def f(a, b, c):
return a, b, c
In [3]:
f(1, 2, 3)
Out[3]:
(1, 2, 3)
In [4]:
f(3, 2, 1)
Out[4]:
(3, 2, 1)
In [5]:
f(c=3, b=2, a=1)
Out[5]:
(1, 2, 3)
Defaults¶
In [6]:
def f(a=1, b=2, c=3):
return a, b, c
In [7]:
f()
Out[7]:
(1, 2, 3)
In [8]:
f(10)
Out[8]:
(10, 2, 3)
In [9]:
f(c=30)
Out[9]:
(1, 2, 30)
Variadic arguments¶
In [10]:
def f(a, *args, **kwargs):
return a, args, kwargs
In [11]:
f(1, 2, 3)
Out[11]:
(1, (2, 3), {})
In [12]:
f(10, 1, 2, 3, d=4, e=5, f=6)
Out[12]:
(10, (1, 2, 3), {'d': 4, 'e': 5, 'f': 6})
Mandatory named arguments¶
In [13]:
def f(a, *, b, c):
return(a, b, c)
In [14]:
f(1, 2, 3)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-14-6f2568884585> in <module>()
----> 1 f(1, 2, 3)
TypeError: f() takes 1 positional argument but 3 were given
In [15]:
f(1, b=2, c=3)
Out[15]:
(1, 2, 3)
Watch out for mutable default arguments¶
In [16]:
def f(a, b=[]):
b.append(a)
return a, b
In [17]:
f(1)
Out[17]:
(1, [1])
The default list argument is set at function definition and persists across function calls¶
In [18]:
f(2)
Out[18]:
(2, [1, 2])
In [19]:
f(2, b=[5,4,3])
Out[19]:
(2, [5, 4, 3, 2])
Idiom for mutable default arguments¶
In [2]:
def f(a, b=None):
if b is None:
b = []
b.append(a)
return a, b
In [3]:
f(1)
Out[3]:
(1, [1])
In [4]:
f(2)
Out[4]:
(2, [2])
Functions are first-class objects¶
In [20]:
funcs = [sum, len, hash]
In [21]:
for func in funcs:
print(func(range(1,5)))
10
4
6312109104812642389
Anonymous functions¶
In [22]:
funcs = [lambda x: x, lambda x: x**2, lambda x: x**3]
In [23]:
for func in funcs:
print(func(10))
10
100
1000
Higher-order function¶
A function that accepts a function as argument¶
In [24]:
def f(a, b, g):
return g(a, b)
In [25]:
f(1, 2, lambda x, y: x + y)
Out[25]:
3
In [26]:
f(1, 2, lambda x, y: x * y)
Out[26]:
2
A function that returns a function¶
In [27]:
def f(a):
def g(x):
return a + x
return g
In [28]:
g = f(3)
In [29]:
g(2)
Out[29]:
5
Dictionary dispatch¶
In [30]:
funcs = {
'left' : lambda p: point(p.x - 1, p.y),
'right': lambda p: point(p.x + 1, p.y),
'up' : lambda p: point(p.x, p.y + 1),
'down' : lambda p: point(p.x, p.y - 1),
}
In [31]:
moves = ['left', 'left', 'up', 'up', 'down', 'up', 'down', 'right']
In [32]:
from collections import namedtuple
point = namedtuple('point', 'x y')
In [33]:
robot = point(0, 0)
In [34]:
robot.x
Out[34]:
0
In [35]:
for move in moves:
robot = funcs[move](robot)
print('%-8s %s' % (move, robot))
left point(x=-1, y=0)
left point(x=-2, y=0)
up point(x=-2, y=1)
up point(x=-2, y=2)
down point(x=-2, y=1)
up point(x=-2, y=2)
down point(x=-2, y=1)
right point(x=-1, y=1)
Map, filter and reduce¶
Map¶
In [36]:
map(lambda x: x**2, range(4))
Out[36]:
<map at 0x10f379f28>
In [37]:
list(map(lambda x: x**2, range(4)))
Out[37]:
[0, 1, 4, 9]
Filter¶
In [38]:
filter(lambda x: x % 2 == 0, range(4))
Out[38]:
<filter at 0x10f3aca20>
In [39]:
list(filter(lambda x: x % 2 == 0, range(4)))
Out[39]:
[0, 2]
Reduce¶
In [40]:
from functools import reduce
In [41]:
reduce(lambda x, y: x + y, range(1, 5))
Out[41]:
10
In [42]:
reduce(lambda x, y: x + y, range(1, 5), 100)
Out[42]:
110
In [43]:
reduce(lambda xs, ys: xs + ys, [[1,2], [3,4], [5,6]])
Out[43]:
[1, 2, 3, 4, 5, 6]
In [44]:
import numpy as np
In [45]:
reduce(lambda xs, ys: xs + ys, [[1,2], [3,4], [5,6]], np.zeros(2))
Out[45]:
array([ 9., 12.])
Decorators¶
In [48]:
import time
In [49]:
def timer(f):
def g(*args, **kwargs):
start = time.time()
ans = f(*args, **kwargs)
elapsed = time.time() - start
return(ans, 'Function took %s seconds' % elapsed)
return g
In [50]:
def fib(n):
a, b = 1, 1
for i in range(n):
a, b = b, a + b
return a
In [51]:
[fib(i) for i in range(11)]
Out[51]:
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
In [52]:
timed_fib = timer(fib)
In [53]:
timed_fib(10)
Out[53]:
(89, 'Function took 3.0994415283203125e-06 seconds')
In [54]:
@timer
def fib(n):
a, b = 1, 1
for i in range(n):
a, b = b, a + b
return a
In [55]:
fib(10)
Out[55]:
(89, 'Function took 3.0994415283203125e-06 seconds')
Useful modules for functional programming¶
Operator¶
Avoids use of lambda
for built-in operators. Often more efficient.
In [56]:
import operator as op
In [57]:
reduce(op.mul, range(1, 5))
Out[57]:
24
In [58]:
xxs = [range(i, i+5) for i in range(5)]
list(map(list, xxs))
Out[58]:
[[0, 1, 2, 3, 4],
[1, 2, 3, 4, 5],
[2, 3, 4, 5, 6],
[3, 4, 5, 6, 7],
[4, 5, 6, 7, 8]]
In [59]:
%timeit -n1000 list(map(op.itemgetter(-1), xxs))
1.78 µs ± 110 ns per loop (mean ± std. dev. of 7 runs, 1000 loops each)
In [60]:
%timeit -n1000 list(map(lambda x: x[-1], xxs))
1.97 µs ± 61.7 ns per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Functools¶
In [61]:
import functools as fn
In [62]:
def f(a, b, c):
return a, b, c
g = fn.partial(f, c = 3)
In [63]:
g(1,2)
Out[63]:
(1, 2, 3)
In [64]:
def recursive_fib(n):
if n==0 or n==1:
return 1
else:
return recursive_fib(n-1) + recursive_fib(n-2)
In [65]:
[recursive_fib(i) for i in range(11)]
Out[65]:
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
In [66]:
recursive_fib(20)
Out[66]:
10946
In [67]:
timeit -n 100 recursive_fib(20)
5.03 ms ± 46.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [68]:
@fn.lru_cache()
def recursive_fib2(n):
if n==0 or n==1:
return 1
else:
return recursive_fib2(n-1) + recursive_fib2(n-2)
In [69]:
recursive_fib2(20)
Out[69]:
10946
In [70]:
timeit -n 100 recursive_fib2(20)
150 ns ± 5.62 ns per loop (mean ± std. dev. of 7 runs, 100 loops each)
Itertools¶
In [71]:
import itertools as it
Slicing infinite streams¶
In [72]:
list(it.islice(it.count(), 10))
Out[72]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
In [73]:
list(it.islice(it.cycle(range(3)), 10))
Out[73]:
[0, 1, 2, 0, 1, 2, 0, 1, 2, 0]
In [74]:
list(it.permutations('abcd'))
Out[74]:
[('a', 'b', 'c', 'd'),
('a', 'b', 'd', 'c'),
('a', 'c', 'b', 'd'),
('a', 'c', 'd', 'b'),
('a', 'd', 'b', 'c'),
('a', 'd', 'c', 'b'),
('b', 'a', 'c', 'd'),
('b', 'a', 'd', 'c'),
('b', 'c', 'a', 'd'),
('b', 'c', 'd', 'a'),
('b', 'd', 'a', 'c'),
('b', 'd', 'c', 'a'),
('c', 'a', 'b', 'd'),
('c', 'a', 'd', 'b'),
('c', 'b', 'a', 'd'),
('c', 'b', 'd', 'a'),
('c', 'd', 'a', 'b'),
('c', 'd', 'b', 'a'),
('d', 'a', 'b', 'c'),
('d', 'a', 'c', 'b'),
('d', 'b', 'a', 'c'),
('d', 'b', 'c', 'a'),
('d', 'c', 'a', 'b'),
('d', 'c', 'b', 'a')]
In [75]:
list(it.combinations('abcd', 3))
Out[75]:
[('a', 'b', 'c'), ('a', 'b', 'd'), ('a', 'c', 'd'), ('b', 'c', 'd')]
In [76]:
[''.join(s) for s in it.combinations('abcd', 3)]
Out[76]:
['abc', 'abd', 'acd', 'bcd']
In [77]:
list(it.takewhile(lambda x: x < 20, it.count(10)))
Out[77]:
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
In [78]:
xxs
Out[78]:
[range(0, 5), range(1, 6), range(2, 7), range(3, 8), range(4, 9)]
In [79]:
list(it.chain([1,2], [3,4], [5,6]))
Out[79]:
[1, 2, 3, 4, 5, 6]
Toolz¶
This is not part of the standard library but very useful.
In [80]:
import toolz as tz
In [141]:
import toolz.curried as tzc
In [81]:
list(tz.sliding_window(3, range(10)))
Out[81]:
[(0, 1, 2),
(1, 2, 3),
(2, 3, 4),
(3, 4, 5),
(4, 5, 6),
(5, 6, 7),
(6, 7, 8),
(7, 8, 9)]
In [82]:
tz.frequencies('tweedledum')
Out[82]:
{'d': 2, 'e': 3, 'l': 1, 'm': 1, 't': 1, 'u': 1, 'w': 1}
In [83]:
cumsum = fn.partial(tz.accumulate, op.add)
list(cumsum(range(10)))
Out[83]:
[0, 1, 3, 6, 10, 15, 21, 28, 36, 45]
In [84]:
list(tz.interleave(['abc', range(3), 'xyz']))
Out[84]:
['a', 0, 'x', 'b', 1, 'y', 'c', 2, 'z']
In [85]:
list(tz.interpose('-', 'abc'))
Out[85]:
['a', '-', 'b', '-', 'c']
In [89]:
list(tz.partition(3, range(10)))
Out[89]:
[(0, 1, 2), (3, 4, 5), (6, 7, 8)]
In [90]:
list(tz.partition(3, range(10), pad='None'))
Out[90]:
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 'None', 'None')]
In [91]:
list(tz.partition_all(3, range(10)))
Out[91]:
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9,)]
In [92]:
list(tz.unique('tweedledum'))
Out[92]:
['t', 'w', 'e', 'd', 'l', 'u', 'm']
In [86]:
fruits = ['apple', 'banana', 'orange', 'pear', 'kiwi', 'apricot']
In [87]:
tz.groupby(len, fruits)
Out[87]:
{4: ['pear', 'kiwi'], 5: ['apple'], 6: ['banana', 'orange'], 7: ['apricot']}
In [88]:
tz.groupby(lambda s: s[0], fruits)
Out[88]:
{'a': ['apple', 'apricot'],
'b': ['banana'],
'k': ['kiwi'],
'o': ['orange'],
'p': ['pear']}
In [94]:
import faker
In [95]:
fake = faker.Faker()
In [129]:
addresses = [fake.address() for i in range(1000)]
In [130]:
addresses[:10]
Out[130]:
['97134 Butler Oval\nAguilarberg, IN 46709-3849',
'154 Lisa Point Apt. 774\nRobertbury, UT 49310',
'54122 Madeline Knolls\nJessicaton, WV 22146',
'68583 Bradley Throughway Apt. 916\nLake Davidville, WI 11324-0572',
'Unit 6904 Box 5565\nDPO AE 63827',
'5109 Brenda Hills Suite 001\nSolishaven, KY 95727-8993',
'15848 Dawson Ports\nWest Jeffstad, PA 33793',
'91291 Angela Valleys Apt. 434\nNorth Debbieville, PR 47717-7165',
'149 Katherine Skyway Suite 676\nMarkchester, TX 64457-9707',
'962 Leonard Row Suite 027\nJodiside, TN 19605']
In [131]:
def get_state(s):
return s.split(',')[-1].strip()[:2]
In [132]:
[get_state(address) for address in addresses][:10]
Out[132]:
['IN', 'UT', 'WV', 'WI', 'Un', 'KY', 'PA', 'PR', 'TX', 'TN']
In [133]:
tz.groupby(get_state, addresses).get('NC')
Out[133]:
['00509 Watson View Suite 948\nJoshuaburgh, NC 50750',
'80032 Kim Summit Suite 826\nLake Patrickmouth, NC 13150',
'891 Randall Mount Suite 207\nSouth Sergio, NC 31915',
'78427 Fuller Estate\nLake Loritown, NC 47978',
'1843 Richard Freeway\nMckayfurt, NC 06130-7973',
'2963 Smith Pine\nPort Dustin, NC 25495',
'590 Pamela Mews\nNew Brandon, NC 43544',
'1085 Brianna Locks\nRossbury, NC 87652-3315',
'5791 Nelson Street Apt. 471\nLake Sarafort, NC 93405-2390',
'5888 Brad Run Suite 087\nGarretthaven, NC 37155',
'0553 Candice Ridge Apt. 505\nBakerville, NC 19344',
'9294 Andrew Hollow\nSnydertown, NC 26055-8332',
'9470 Elizabeth Stream\nHeidiburgh, NC 87725-4062',
'21597 Lane Island\nMarcstad, NC 75752']
In [134]:
from math import sqrt
In [139]:
list(it.islice(tz.iterate(sqrt, 100), 10))
Out[139]:
[100,
10.0,
3.1622776601683795,
1.7782794100389228,
1.333521432163324,
1.1547819846894583,
1.0746078283213176,
1.036632928437698,
1.018151721718182,
1.0090350448414476]
In [146]:
tz.pipe(range(10),
tzc.take(10),
tzc.tail(5))
Out[146]:
(5, 6, 7, 8, 9)
Funcy¶
This is very similar to toolz
. You can use either one, or even both
together with itertools
and functools
.
In [150]:
import funcy
In [153]:
list(funcy.flatten([[[1,[2,3]]], [4,5], [6,[7,[8,9],[10],11]]]))
Out[153]:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
In [166]:
funcy.take(5, funcy.mapcat(str.splitlines, addresses))
Out[166]:
['97134 Butler Oval',
'Aguilarberg, IN 46709-3849',
'154 Lisa Point Apt. 774',
'Robertbury, UT 49310',
'54122 Madeline Knolls']
In [ ]: