Python Functions¶
In [1]:
import numpy as np
Custom functions¶
Anatomy¶
name, arguments, docstring, body, return statement
In [2]:
def func_name(arg1, arg2):
"""Docstring starts wtih a short description.
May have more information here.
arg1 = something
arg2 = somehting
Returns something
Example usage:
func_name(1, 2)
"""
result = arg1 + arg2
return result
In [3]:
help(func_name)
Help on function func_name in module __main__:
func_name(arg1, arg2)
Docstring starts wtih a short description.
May have more information here.
arg1 = something
arg2 = somehting
Returns something
Example usage:
func_name(1, 2)
Function arguments¶
place, keyword, keyword-only, defaults, mutatble an immutable arguments
In [4]:
def f(a, b, c, *args, **kwargs):
return a, b, c, args, kwargs
In [5]:
f(1, 2, 3, 4, 5, 6, x=7, y=8, z=9)
Out[5]:
(1, 2, 3, (4, 5, 6), {'x': 7, 'y': 8, 'z': 9})
In [6]:
def g(a, b, c, *, x, y, z):
return a, b, c, x, y, z
In [7]:
try:
g(1,2,3,4,5,6)
except TypeError as e:
print(e)
g() takes 3 positional arguments but 6 were given
In [8]:
g(1,2,3,x=4,y=5,z=6)
Out[8]:
(1, 2, 3, 4, 5, 6)
In [9]:
def h(a=1, b=2, c=3):
return a, b, c
In [10]:
h()
Out[10]:
(1, 2, 3)
In [11]:
h(b=9)
Out[11]:
(1, 9, 3)
In [12]:
h(7,8,9)
Out[12]:
(7, 8, 9)
Default mutable argumnet¶
binding is fixed at function definition, the default=None idiom
In [13]:
def f(a, x=[]):
x.append(a)
return x
In [14]:
f(1)
Out[14]:
[1]
In [15]:
f(2)
Out[15]:
[1, 2]
In [16]:
def f(a, x=None):
if x is None:
x = []
x.append(a)
return x
In [17]:
f(1)
Out[17]:
[1]
In [18]:
f(2)
Out[18]:
[2]
Pure functions¶
deterministic, no side effects
In [19]:
def f1(x):
"""Pure."""
return x**2
In [20]:
def f2(x):
"""Pure if we ignore local state change.
The x in the function baheaves like a copy.
"""
x = x**2
return x
In [21]:
def f3(x):
"""Impure if x is mutable.
Augmented assignemnt is an in-place operation for mutable structures."""
x **= 2
return x
In [22]:
a = 2
b = np.array([1,2,3])
In [23]:
f1(a), a
Out[23]:
(4, 2)
In [24]:
f1(b), b
Out[24]:
(array([1, 4, 9]), array([1, 2, 3]))
In [25]:
f2(a), a
Out[25]:
(4, 2)
In [26]:
f2(b), b
Out[26]:
(array([1, 4, 9]), array([1, 2, 3]))
In [27]:
f3(a), a
Out[27]:
(4, 2)
In [28]:
f3(b), b
Out[28]:
(array([1, 4, 9]), array([1, 4, 9]))
In [29]:
def f4():
"""Stochastic functions are tehcnically impure
since a global seed is changed between function calls."""
import random
return random.randint(0,10)
In [30]:
f4(), f4(), f4()
Out[30]:
(1, 0, 8)
Recursive functions¶
Euclidean GCD algorithm
gcd(a, 0) = a
gcd(a, b) = gcd(b, a mod b)
In [31]:
def factorial(n):
"""Simple recursive funciton."""
if n == 0:
return 1
else:
return n * factorial(n-1)
In [32]:
factorial(4)
Out[32]:
24
In [33]:
def factorial1(n):
"""Non-recursive version."""
s = 1
for i in range(1, n+1):
s *= i
return s
In [34]:
factorial1(4)
Out[34]:
24
In [35]:
def gcd(a, b):
if b == 0:
return a
else:
return gcd(b, a % b)
In [36]:
gcd(16, 24)
Out[36]:
8
Generators¶
yield and laziness, infinite streams
In [37]:
def count(n=0):
while True:
yield n
n += 1
In [38]:
for i in count(10):
print(i)
if i >= 15:
break
10
11
12
13
14
15
In [39]:
from itertools import islice
In [40]:
list(islice(count(), 10, 15))
Out[40]:
[10, 11, 12, 13, 14]
In [41]:
def updown(n):
yield from range(n)
yield from range(n, 0, -1)
In [42]:
updown(5)
Out[42]:
<generator object updown at 0x109e54728>
In [43]:
list(updown(5))
Out[43]:
[0, 1, 2, 3, 4, 5, 4, 3, 2, 1]
First class functions¶
functions as arguments, functions as return values
In [44]:
def double(x):
return x*2
def twice(x, func):
return func(func(x))
In [45]:
twice(3, double)
Out[45]:
12
Example from standard library
In [46]:
xs = 'banana apple guava'.split()
In [47]:
xs
Out[47]:
['banana', 'apple', 'guava']
In [48]:
sorted(xs)
Out[48]:
['apple', 'banana', 'guava']
In [49]:
sorted(xs, key=lambda s: s.count('a'))
Out[49]:
['apple', 'guava', 'banana']
In [50]:
def f(n):
def g():
print("hello")
def h():
print("goodbye")
if n == 0:
return g
else:
return h
In [51]:
g = f(0)
g()
hello
In [52]:
h = f(1)
h()
goodbye
Function dispatch¶
Poor man’s switch statement
In [53]:
def add(x, y):
return x + y
def mul(x, y):
return x * y
In [54]:
ops = {
'a': add,
'm': mul
}
In [55]:
items = zip('aammaammam', range(10), range(10))
In [56]:
for item in items:
key, x, y = item
op = ops[key]
print(key, x, y, op(x, y))
a 0 0 0
a 1 1 2
m 2 2 4
m 3 3 9
a 4 4 8
a 5 5 10
m 6 6 36
m 7 7 49
a 8 8 16
m 9 9 81
Closure¶
Capture of argument in enclosing scope
In [57]:
def f(x):
def g(y):
return x + y
return g
In [58]:
f1 = f(0)
f2 = f(10)
In [59]:
f1(5), f2(5)
Out[59]:
(5, 15)
Decorators¶
A timing decorator
In [60]:
def timer(f):
import time
def g(*args, **kwargs):
tic = time.time()
res = f(*args, **kwargs)
toc = time.time()
return res, toc-tic
return g
In [61]:
def f(n):
s = 0
for i in range(n):
s += i
return s
In [62]:
timed_f = timer(f)
In [63]:
timed_f(100000)
Out[63]:
(4999950000, 0.008235931396484375)
Decorator syntax
In [64]:
@timer
def g(n):
s = 0
for i in range(n):
s += i
return s
In [65]:
g(100000)
Out[65]:
(4999950000, 0.008067846298217773)
Anonymous functions¶
Short, one-use lambdas
In [66]:
f = lambda x: x**2
In [67]:
f(3)
Out[67]:
9
In [68]:
g = lambda x, y: x+y
In [69]:
g(3,4)
Out[69]:
7
Map, filter and reduce¶
Funcitonal building blocks
In [70]:
xs = range(10)
list(map(lambda x: x**2, xs))
Out[70]:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
In [71]:
list(filter(lambda x: x%2 == 0, xs))
Out[71]:
[0, 2, 4, 6, 8]
In [72]:
from functools import reduce
In [73]:
reduce(lambda x, y: x+y, xs)
Out[73]:
45
In [74]:
reduce(lambda x, y: x+y, xs, 100)
Out[74]:
145
Functional modules in the standard library¶
itertools, functional and operator
In [75]:
import operator as op
In [76]:
reduce(op.add, range(10))
Out[76]:
45
In [77]:
import itertools as it
In [78]:
list(it.islice(it.cycle([1,2,3]), 1, 10))
Out[78]:
[2, 3, 1, 2, 3, 1, 2, 3, 1]
In [79]:
list(it.permutations('abc', 2))
Out[79]:
[('a', 'b'), ('a', 'c'), ('b', 'a'), ('b', 'c'), ('c', 'a'), ('c', 'b')]
In [80]:
list(it.combinations('abc', 2))
Out[80]:
[('a', 'b'), ('a', 'c'), ('b', 'c')]
In [81]:
from functools import partial, lru_cache
In [82]:
def f(a, b, c):
return a + b + c
In [83]:
g = partial(f, b = 2, c=3)
In [84]:
g(1)
Out[84]:
6
In [85]:
def fib(n, trace=False):
if trace:
print("fib(%d)" % n, end=',')
if n <= 2:
return 1
else:
return fib(n-1, trace) + fib(n-2, trace)
In [86]:
fib(10, True)
fib(10),fib(9),fib(8),fib(7),fib(6),fib(5),fib(4),fib(3),fib(2),fib(1),fib(2),fib(3),fib(2),fib(1),fib(4),fib(3),fib(2),fib(1),fib(2),fib(5),fib(4),fib(3),fib(2),fib(1),fib(2),fib(3),fib(2),fib(1),fib(6),fib(5),fib(4),fib(3),fib(2),fib(1),fib(2),fib(3),fib(2),fib(1),fib(4),fib(3),fib(2),fib(1),fib(2),fib(7),fib(6),fib(5),fib(4),fib(3),fib(2),fib(1),fib(2),fib(3),fib(2),fib(1),fib(4),fib(3),fib(2),fib(1),fib(2),fib(5),fib(4),fib(3),fib(2),fib(1),fib(2),fib(3),fib(2),fib(1),fib(8),fib(7),fib(6),fib(5),fib(4),fib(3),fib(2),fib(1),fib(2),fib(3),fib(2),fib(1),fib(4),fib(3),fib(2),fib(1),fib(2),fib(5),fib(4),fib(3),fib(2),fib(1),fib(2),fib(3),fib(2),fib(1),fib(6),fib(5),fib(4),fib(3),fib(2),fib(1),fib(2),fib(3),fib(2),fib(1),fib(4),fib(3),fib(2),fib(1),fib(2),
Out[86]:
55
In [87]:
%timeit -r1 -n100 fib(20)
2.93 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 100 loops each)
In [88]:
@lru_cache(3)
def fib1(n, trace=False):
if trace:
print("fib(%d)" % n, end=',')
if n <= 2:
return 1
else:
return fib1(n-1, trace) + fib1(n-2, trace)
In [89]:
fib1(10, True)
fib(10),fib(9),fib(8),fib(7),fib(6),fib(5),fib(4),fib(3),fib(2),fib(1),
Out[89]:
55
In [90]:
%timeit -r1 -n100 fib1(20)
348 ns ± 0 ns per loop (mean ± std. dev. of 1 run, 100 loops each)
Using toolz
¶
funcitonal power tools
In [91]:
import toolz as tz
import toolz.curried as c
Find the 5 most common sequences of length 3 in the dna variable.
In [92]:
dna = np.random.choice(list('ACTG'), (10,80), p=[.1,.2,.3,.4])
In [93]:
dna
Out[93]:
array([['T', 'C', 'G', 'G', 'T', 'G', 'C', 'C', 'G', 'T', 'G', 'G', 'C',
'G', 'C', 'C', 'C', 'C', 'T', 'G', 'A', 'T', 'G', 'T', 'G', 'G',
'T', 'G', 'C', 'C', 'C', 'T', 'G', 'G', 'T', 'C', 'T', 'T', 'G',
'C', 'G', 'T', 'C', 'G', 'T', 'T', 'T', 'G', 'G', 'T', 'G', 'T',
'G', 'T', 'G', 'G', 'G', 'G', 'C', 'G', 'A', 'G', 'G', 'G', 'G',
'T', 'G', 'T', 'T', 'T', 'G', 'T', 'G', 'G', 'C', 'A', 'T', 'C',
'T', 'G'],
['G', 'A', 'T', 'G', 'C', 'C', 'G', 'A', 'G', 'T', 'G', 'G', 'A',
'G', 'G', 'C', 'G', 'C', 'G', 'G', 'G', 'G', 'C', 'G', 'T', 'G',
'G', 'T', 'G', 'G', 'G', 'G', 'C', 'G', 'G', 'G', 'C', 'C', 'G',
'T', 'C', 'A', 'A', 'C', 'T', 'T', 'T', 'G', 'G', 'G', 'G', 'G',
'G', 'G', 'G', 'G', 'A', 'G', 'G', 'C', 'C', 'G', 'G', 'T', 'T',
'A', 'T', 'T', 'C', 'G', 'G', 'G', 'A', 'C', 'T', 'G', 'T', 'G',
'C', 'C'],
['C', 'C', 'G', 'C', 'T', 'G', 'G', 'T', 'C', 'G', 'C', 'G', 'G',
'G', 'C', 'G', 'G', 'T', 'T', 'T', 'C', 'T', 'C', 'G', 'T', 'G',
'A', 'G', 'G', 'C', 'G', 'T', 'G', 'G', 'G', 'T', 'T', 'G', 'G',
'C', 'C', 'G', 'T', 'T', 'G', 'T', 'C', 'T', 'C', 'C', 'G', 'C',
'T', 'T', 'C', 'G', 'G', 'T', 'A', 'G', 'C', 'T', 'T', 'T', 'G',
'G', 'G', 'A', 'T', 'T', 'T', 'A', 'G', 'G', 'G', 'C', 'T', 'G',
'C', 'G'],
['T', 'T', 'C', 'G', 'T', 'T', 'T', 'C', 'C', 'C', 'G', 'G', 'T',
'C', 'T', 'T', 'G', 'T', 'G', 'T', 'G', 'C', 'G', 'G', 'C', 'G',
'G', 'C', 'T', 'C', 'G', 'T', 'G', 'T', 'T', 'C', 'G', 'C', 'T',
'T', 'T', 'T', 'G', 'T', 'C', 'G', 'C', 'T', 'A', 'T', 'G', 'G',
'A', 'T', 'G', 'C', 'G', 'G', 'G', 'G', 'C', 'T', 'T', 'C', 'T',
'G', 'G', 'G', 'G', 'G', 'A', 'T', 'A', 'C', 'T', 'G', 'C', 'C',
'G', 'T'],
['G', 'C', 'G', 'G', 'G', 'T', 'T', 'T', 'G', 'C', 'G', 'G', 'G',
'C', 'G', 'T', 'A', 'T', 'T', 'T', 'C', 'G', 'G', 'G', 'G', 'G',
'T', 'T', 'T', 'T', 'T', 'G', 'T', 'A', 'C', 'C', 'G', 'G', 'T',
'C', 'G', 'T', 'T', 'G', 'G', 'T', 'G', 'G', 'G', 'T', 'T', 'G',
'C', 'T', 'C', 'G', 'G', 'C', 'C', 'T', 'G', 'T', 'G', 'G', 'T',
'C', 'A', 'G', 'T', 'G', 'C', 'G', 'G', 'A', 'G', 'C', 'C', 'G',
'T', 'G'],
['G', 'A', 'T', 'G', 'C', 'T', 'T', 'T', 'C', 'T', 'T', 'G', 'G',
'C', 'C', 'A', 'G', 'G', 'T', 'T', 'T', 'G', 'C', 'C', 'T', 'G',
'T', 'C', 'C', 'C', 'G', 'G', 'T', 'T', 'G', 'C', 'C', 'G', 'A',
'T', 'G', 'T', 'T', 'C', 'G', 'G', 'C', 'T', 'T', 'T', 'G', 'T',
'G', 'G', 'G', 'C', 'T', 'G', 'C', 'G', 'C', 'G', 'T', 'A', 'C',
'T', 'G', 'G', 'G', 'A', 'G', 'C', 'C', 'T', 'G', 'G', 'T', 'G',
'C', 'A'],
['C', 'G', 'T', 'T', 'G', 'A', 'G', 'G', 'T', 'T', 'A', 'T', 'C',
'G', 'A', 'G', 'C', 'T', 'C', 'C', 'G', 'T', 'G', 'G', 'G', 'T',
'T', 'G', 'G', 'G', 'G', 'G', 'A', 'G', 'A', 'G', 'G', 'G', 'G',
'C', 'T', 'A', 'T', 'T', 'T', 'G', 'G', 'T', 'G', 'T', 'G', 'G',
'T', 'G', 'A', 'G', 'G', 'G', 'G', 'G', 'A', 'G', 'T', 'C', 'T',
'G', 'C', 'T', 'G', 'G', 'C', 'G', 'C', 'T', 'A', 'G', 'T', 'G',
'T', 'G'],
['T', 'A', 'T', 'T', 'C', 'G', 'A', 'G', 'A', 'T', 'A', 'T', 'C',
'T', 'G', 'C', 'C', 'C', 'C', 'C', 'C', 'G', 'C', 'T', 'G', 'G',
'G', 'A', 'G', 'T', 'G', 'G', 'T', 'T', 'A', 'C', 'G', 'G', 'C',
'T', 'G', 'C', 'T', 'C', 'T', 'C', 'G', 'T', 'C', 'C', 'A', 'G',
'T', 'G', 'T', 'T', 'T', 'A', 'G', 'C', 'T', 'T', 'G', 'G', 'C',
'G', 'T', 'G', 'C', 'C', 'A', 'T', 'T', 'G', 'T', 'G', 'C', 'G',
'G', 'T'],
['G', 'T', 'T', 'A', 'G', 'G', 'G', 'C', 'C', 'A', 'G', 'T', 'G',
'C', 'C', 'T', 'G', 'G', 'G', 'G', 'G', 'C', 'G', 'T', 'A', 'T',
'C', 'G', 'G', 'T', 'G', 'G', 'G', 'A', 'T', 'C', 'T', 'C', 'G',
'T', 'A', 'C', 'C', 'G', 'T', 'G', 'G', 'C', 'T', 'T', 'G', 'G',
'G', 'T', 'T', 'T', 'C', 'G', 'T', 'C', 'G', 'C', 'C', 'C', 'G',
'G', 'G', 'C', 'C', 'T', 'C', 'G', 'T', 'T', 'C', 'G', 'T', 'C',
'C', 'G'],
['G', 'C', 'T', 'G', 'C', 'G', 'T', 'G', 'T', 'G', 'G', 'C', 'T',
'C', 'G', 'G', 'T', 'G', 'G', 'G', 'G', 'A', 'T', 'A', 'G', 'T',
'T', 'T', 'T', 'T', 'G', 'G', 'G', 'G', 'G', 'C', 'A', 'G', 'A',
'G', 'A', 'T', 'A', 'C', 'G', 'T', 'G', 'T', 'C', 'T', 'G', 'G',
'G', 'A', 'G', 'T', 'G', 'G', 'G', 'T', 'G', 'G', 'T', 'G', 'T',
'C', 'T', 'T', 'T', 'T', 'C', 'A', 'G', 'G', 'T', 'C', 'G', 'T',
'G', 'G']],
dtype='<U1')
In [94]:
tz.pipe(
dna,
c.map(lambda s: ''.join(s)),
list
)
Out[94]:
['TCGGTGCCGTGGCGCCCCTGATGTGGTGCCCTGGTCTTGCGTCGTTTGGTGTGTGGGGCGAGGGGTGTTTGTGGCATCTG',
'GATGCCGAGTGGAGGCGCGGGGCGTGGTGGGGCGGGCCGTCAACTTTGGGGGGGGGAGGCCGGTTATTCGGGACTGTGCC',
'CCGCTGGTCGCGGGCGGTTTCTCGTGAGGCGTGGGTTGGCCGTTGTCTCCGCTTCGGTAGCTTTGGGATTTAGGGCTGCG',
'TTCGTTTCCCGGTCTTGTGTGCGGCGGCTCGTGTTCGCTTTTGTCGCTATGGATGCGGGGCTTCTGGGGGATACTGCCGT',
'GCGGGTTTGCGGGCGTATTTCGGGGGTTTTTGTACCGGTCGTTGGTGGGTTGCTCGGCCTGTGGTCAGTGCGGAGCCGTG',
'GATGCTTTCTTGGCCAGGTTTGCCTGTCCCGGTTGCCGATGTTCGGCTTTGTGGGCTGCGCGTACTGGGAGCCTGGTGCA',
'CGTTGAGGTTATCGAGCTCCGTGGGTTGGGGGAGAGGGGCTATTTGGTGTGGTGAGGGGGAGTCTGCTGGCGCTAGTGTG',
'TATTCGAGATATCTGCCCCCCGCTGGGAGTGGTTACGGCTGCTCTCGTCCAGTGTTTAGCTTGGCGTGCCATTGTGCGGT',
'GTTAGGGCCAGTGCCTGGGGGCGTATCGGTGGGATCTCGTACCGTGGCTTGGGTTTCGTCGCCCGGGCCTCGTTCGTCCG',
'GCTGCGTGTGGCTCGGTGGGGATAGTTTTTGGGGGCAGAGATACGTGTCTGGGAGTGGGTGGTGTCTTTTCAGGTCGTGG']
In [95]:
res = tz.pipe(
dna,
c.map(lambda s: ''.join(s)),
lambda s: ''.join(s),
c.sliding_window(3),
c.map(lambda s: ''.join(s)),
tz.frequencies
)
In [96]:
[(k,v) for i, (k, v) in enumerate(sorted(res.items(), key=lambda x: -x[1])) if i < 5]
Out[96]:
[('GGG', 58), ('GTG', 47), ('TGG', 44), ('GGT', 33), ('GGC', 31)]
Function annotations and type hints¶
Function annotations and type hints are optional and meant for 3rd party libraries (e.g. a static type checker or JIT compiler). They are NOT enforced at runtime.
Notice the type annotation, default value and return type.
In [97]:
def f(a: str = "hello") -> bool:
return a.islower()
In [98]:
f()
Out[98]:
True
In [99]:
f("hello")
Out[99]:
True
In [100]:
f("Hello")
Out[100]:
False
Function annotations can be accessed through a special attribute.
In [101]:
f.__annotations__
Out[101]:
{'a': str, 'return': bool}
Type and function annotations are NOT enforced. In fact, the Python interpreter essentially ignores them.
In [102]:
def f(x: int) -> int:
return x + x
In [103]:
f("hello")
Out[103]:
'hellohello'
For more types, import from the typing
module
In [104]:
from typing import Sequence, TypeVar
In [105]:
from functools import reduce
import operator as op
In [106]:
T = TypeVar('T')
def f(xs: Sequence[T]) -> T:
return reduce(op.add, xs)
In [107]:
f([1,2,3])
Out[107]:
6
In [108]:
f({1., 2., 3.})
Out[108]:
6.0
In [109]:
f(('a', 'b', 'c'))
Out[109]:
'abc'