12.2. Functional Programming¶
12.2.1. Pure function¶
Function which returns always the same results based on the same argument.
random.randint()
- Not purepow()
- Pure
12.2.2. Lambda - Anonymous functions¶
Example 1:
DATA = [1, 2, 3, 4]
def is_even(x):
if x % 2 == 0:
return True
else:
return False
result = filter(is_even, DATA)
print(list(result))
# [2, 4]
DATA = [1, 2, 3, 4]
result = filter(lambda x: x % 2 == 0, DATA)
print(list(result))
# [2, 4]
Example 2:
DATA = [{'user': 'twardowski', 'uid': 1000},
{'user': 'root', 'uid': 0}]
def is_system_user(data):
if data['uid'] < 1000:
return True
else:
return False
result = []
for user in DATA:
if is_system_user(user):
result.append(user)
print(result)
# [{'user': 'root', 'uid': 0}]
DATA = [{'user': 'twardowski', 'uid': 1000},
{'user': 'root', 'uid': 0}]
result = filter(lambda x: x['uid'] < 1000, DATA)
print(list(result))
# [{'user': 'root', 'uid': 0}]
Monkey patching:
class Astronaut:
pass
jan = Astronaut()
jan.say_hello = lambda: print('hello')
jan.say_hello()
12.2.3. Function Passing¶
print(
tuple(
filter(lambda x: x[1]<100,
enumerate(
filter(lambda x: x%2==0,
map(lambda x: pow(x, 2),
map(float,
(x for x in range(0, 34) if x % 3 == 0
))))))))
12.2.4. functools
¶
Reduce:
from functools import reduce
DATA = [1, 2, 3, 4, 5]
def add(x, y):
return (x + y)
result = reduce(add, DATA)
print(result)
# 15
from functools import reduce
DATA = [1, 2, 3, 4, 5]
result = reduce(lambda x, y: x + y, DATA)
print(result)
# 15
lru_cache
:
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(num):
if num < 2:
return num
else:
return fib(num-1) + fib(num-2)
fib(16)
# 987
fib
# <functools._lru_cache_wrapper object at 0x11cce6730>
fib.cache_info()
# CacheInfo(hits=14, misses=17, maxsize=None, currsize=17)
memoize:
def factorial(n):
if not hasattr(factorial, '__cache__'):
factorial.__cache__ = {1: 1}
if not n in factorial.__cache__:
factorial.__cache__[n] = n * factorial(n - 1)
return factorial.__cache__[n]
factorial(5)
# 120
factorial.__cache__
# {1:1, 2:2, 3:6, 4:24, 5:120}
def memoize(function):
from functools import wraps
memo = {}
@wraps(function)
def wrapper(*args):
if args in memo:
return memo[args]
else:
rv = function(*args)
memo[args] = rv
return rv
return wrapper
@memoize
def fibonacci(n):
if n < 2: return n
return fibonacci(n - 1) + fibonacci(n - 2)
fibonacci(25)
partial:
Create alias function and its arguments
Useful when you need to pass function with arguments to for example
map
orfilter
from functools import partial
basetwo = partial(int, base=2)
basetwo.__doc__ = 'Convert base 2 string to an int.'
basetwo('10010')
# 18
partialmethod:
class Cell(object):
def __init__(self):
self._alive = False
@property
def alive(self):
return self._alive
def set_state(self, state):
self._alive = bool(state)
set_alive = partialmethod(set_state, True)
set_dead = partialmethod(set_state, False)
c = Cell()
c.alive
# False
c.set_alive()
c.alive
# True
reduce:
Apply function of two arguments cumulatively to the items of iterable, from left to right, so as to reduce the iterable to a single value. For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates ((((1+2)+3)+4)+5). The left argument, x, is the accumulated value and the right argument, y, is the update value from the iterable. If the optional initializer is present, it is placed before the items of the iterable in the calculation, and serves as a default when the iterable is empty. If initializer is not given and iterable contains only one item, the first item is returned.
Roughly equivalent to:
def reduce(function, iterable, initializer=None):
it = iter(iterable)
if initializer is None:
value = next(it)
else:
value = initializer
for element in it:
value = function(value, element)
return value
singledispatch:
Since Python 3.4
Overload a method
Python will choose function to run based on argument type
from functools import singledispatch
@singledispatch
def celsius_to_kelvin(arg):
raise NotImplementedError('Argument must be int or list')
@celsius_to_kelvin.register
def _(degree: int):
return degree + 273.15
@celsius_to_kelvin.register
def _(degrees: list):
return [d+273.15 for d in degrees]
celsius_to_kelvin(1)
# 274.15
celsius_to_kelvin([1,2])
# [274.15, 275.15]
celsius_to_kelvin((1,2))
# Traceback (most recent call last):
# NotImplementedError: Argument must be int or list
singledispatchmethod:
Since Python 3.8
Overload a method
Python will choose method to run based on argument type
from functools import singledispatchmethod
class Converter:
@singledispatchmethod
def celsius_to_kelvin(arg):
raise NotImplementedError('Argument must be int or list')
@celsius_to_kelvin.register
def _(self, degree: int):
return degree + 273.15
@celsius_to_kelvin.register
def _(self, degrees: list):
return [d+273.15 for d in degrees]
conv = Converter()
conv.celsius_to_kelvin(1)
# 274.15
conv.celsius_to_kelvin([1,2])
# [274.15, 275.15]
conv.celsius_to_kelvin((1,2))
# Traceback (most recent call last):
# NotImplementedError: Argument must be int or list
12.2.5. Callback¶
def http(obj):
response = requests.request(
method=obj.method,
data=obj.data,
path=obj.path)
if response == 200:
return obj.on_success(response)
else:
return obj.on_error(response)
class Request:
method = 'GET'
path = '/index'
data = None
def on_success(self, response):
print('Success!')
def on_error(self, response):
print('Error')
http(
Request()
)
12.2.6. Assignments¶
"""
* Assignment: Functional Map Filter Lambda
* Complexity: easy
* Lines of code: 10 lines
* Time: 13 min
English:
TODO: English Translation
Polish:
1. Używając generatora zbuduj listę zawierającą wszystkie liczby podzielne przez 3 z zakresu od 1 do 33:
2. Używając funkcji `filter()` usuń z niej wszystkie liczby parzyste
3. Używając wyrażenia `lambda` i funkcji `map()` podnieś wszystkie elementy tak otrzymanej listy do sześcianu
4. Odpowiednio używając funkcji `sum()` i `len()` oblicz średnią arytmetyczną z elementów tak otrzymanej listy.
"""
# Given
NUMBERS = range(1, 34)
"""
* Assignment: Balanced Brackets
* Complexity: medium
* Lines of code: 10 lines
* Time: 13 min
English:
1. Create function which checks if brackets are balanced
2. Brackets are balanced, when each opening bracket has closing pair
3. Use recursion
4. Types of brackets:
a. round: `(` i `)`
b. square: `[` i `]`
c. curly `{` i `}`
d. angle `<` i `>`
Polish:
1. Stwórz funkcję, która sprawdzi czy nawiasy są zbalansowane
2. Nawiasy są zbalansowane, gdy każdy otwierany nawias ma zamykającą parę
3. Użyj rekurencji
4. Typy nawiasów:
a. okrągłe: `(` i `)`
b. kwadratowe: `[` i `]`
c. klamrowe `{` i `}`
d. trójkątne `<` i `>`
Tests:
>>> is_bracket_balanced('{}')
True
>>> is_bracket_balanced('()')
True
>>> is_bracket_balanced('[]')
True
>>> is_bracket_balanced('<>')
True
>>> is_bracket_balanced('')
True
>>> is_bracket_balanced('(')
False
>>> is_bracket_balanced('}')
False
>>> is_bracket_balanced('(]')
False
>>> is_bracket_balanced('([)')
False
>>> is_bracket_balanced('[()')
False
>>> is_bracket_balanced('{()[]}')
True
>>> is_bracket_balanced('() [] () ([]()[])')
True
>>> is_bracket_balanced("( (] ([)]")
False
"""
# Given
BRACKET_OPEN = ('(', '{', '[', '<')
BRACKET_CLOSE = (')', '}', ']', '>')
PAIRS = dict(zip(BRACKET_OPEN, BRACKET_CLOSE))