4. Чистые функции
def p_fact(n): # pure
return 1 if n < 2 else n * p_fact(n - 1)
def c_fact(n): # referentially transparent
try:
return c_fact._cache[n]
except KeyError:
x = c_fact._cache[n] = p_fact(n)
return x
c_fact._cache = {}
def o_fact(n): # io + logic - bad
f = p_fact(n)
print("{}! = {}".format(n, f)
return f
calculated_factorials = {}
def g_fact(n): # side effents
f = fact(n)
calculated_factorials[n] = f
return f
5. Побочные эффекты – плохо?
● Могут приводить к гейзенбагам.
● Без побочных эффектов никак не обойтись.
● Даже в pure-functional языках.
● Но их можно локализовать.
● Система типов может в этом помогать.
6. Ошибки начинающих
a = [[]] * 3
a[0].append(1)
print(a)
# [[1], [1], [1]] - WTF?
class Foo:
g = {}
def __init__(self, x):
self.g[x] = x
f1, f2 = Foo(5), Foo(6)
print(f2.x)
# {5:5, 6:6} - WTF
f = [lambda: i for i in [1, 2]]
for x in f:
print(x(), end="")
# 22 – WTF?
def f(x, a=[]):
a.append(x)
print(a)
print(f(1))
print(f(2))
# [1, 2] – WTF?
`
10. “Перестаньте писать классы”
class Greeting(object):
def __init__(self, word):
self.word = word
def greet(self, name):
return "{}, {}!".format(self.word, name)
greet_hello = Greeting('Hello').greet
# -- VERSUS --
def greet(word, name):
return "{}, {}!".format(word, name)
greet_hello = functools.partial(greet, 'Hello')
11. Высокоуровневые функции ввода-вывода
def process_line(line):
a, b = map(int, line.split())
return "out: " + str(a + b)
def interact(f):
for line in sys.stdin:
print(f(line))
def run():
interact(process_line)
Какой вариант лучше?
def print_result(val):
print("out:" + str(val))
def run():
for line in sys.stdin:
a, b = map(int, line.split())
print_result(a + b)
12. PEP 443 – singledispatch – in stdlib since 3.4
from singledispatch import singledispatch
@singledispatch
def move_point(point, x, y): ...
@move_point.register(tuple)
def _(point, x, y): ...
@move_point.register(Point)
def _(point, x, y): ...
16. print map(lambda x: x * 2, [1, 2, 3])
# внутри другой функции - ERROR!
y = 2
print map(lambda x: x * y, [1, 2, 3])
# workaround
print map(lambda x, y=y: x * y, [1, 2, 3])
January 1994 – Python 1.0
Origins of lambda
17. Origins of lambda
April 2001 – Python 2.1 – замыкания
December 2008 – Python 3.0
● Больше итераторов (map, filter, dict values/keys).
● Хотели убрать lambda – оставили!
● Добавили nonlocal – мутабельные замыкания.
● Убрали reduce из builtins.
21. Функции высших порядков
Функции принимают другие функции в
качестве аргументов.
map, filter, timeit, iter...
Все декораторы!
trace, memoize, locking, transaction...
23. Функции – тоже данные
● Можно хранить в структурах данных.
● Список или словарь функций – хорошо!
● Не забываем про замыкания.
● Функции – атомарные значения.
26. Readability counts
ss = ["list", "of", "words"]
# 1 – imperative
tlen = 0
for s in ss:
tlen += len(s)
# 2 – so ugly
reduce(lambda l,r: l+r, map(lambda s: len(s), ss))
# 3 – not bad
reduce(add, map(len, ss))
# 4 – good
sum(map(len, ss))
27. Рекурсия
● Во многих функциональных языках –
единственная операция для огранизации
цикла.
● Многие алгоритмы проще выражаются в
рамках рекурсии.
● Не типична для Python программ.
28. Хвостовая рекурсия
def factorial(n):
if n:
return n * factorial(n - 1)
else:
return 1
def factorial(n, acc=1):
if n:
return factorial(n – 1, n * acc)
else:
return acc
29. TCO в Python
● Мешает красивым стектрейсам.
● Это оптимизация – деталь реализации, а не
элемент языка.
● Рекурсия – не базовая операция в
программировании.
● Мешает динамичная сущность Python'а.
30. Trampoline
def trampoline(f):
def wrapper(*args, **kwargs):
ff = f(*args, **kwargs)
while callable(ff): ff = ff()
return ff
return wrapper
def factorial(n, acc=0):
if n:
return lambda: factorial(n - 1, n * acc)
else:
return acc
print(trampoline(factorial)(10))
Эмулируем
хвостовую рекурсию
Нельзя использовать
как декоратор!
31. Trampoline - варианты
def recur(*args, **kwargs): ...
@trampoline
def factorial(n, acc=0):
if n:
return recur(
n - 1, n * acc)
else:
return acc
@trampoline
def factorial(n, acc=0):
if n:
yield factorial(
n - 1, n * acc)
else:
return acc
Py3k only
32. Сами реализуем TCO
➔ Модификация байткода.
➔ Модификация исходного кода (препроцессинг).
➔ Анализ стека (sys._getframe()).
➔ Хранение локального состояния (threading.local).
Оптимизируем хвостовой вызов без
модификации самой функции
33. Используем threading.local
_FunCall = namedtuple(
'_FunCall', 'func, args, kwargs')
def tco(f):
tl = threading.local()
tl.trampolined = False
def func(*args, **kwargs):
if not tl.trampolined:
try:
tl.trampolined = True
while 1:
res = f(*args, **kwargs)
if isinstance(res, _FunCall) and
res.func is f:
args = res.args
kwargs = res.kwargs
else:
return res
finally:
tl.trampolined = False
else:
return _FunCall(f, args, kwargs)
return func
Проще – быстрее?
Не обрабатывает
ситауцию
f → k → f
34. Используем sys._getframe()
TailRecurseCall = collections.namedtuple(
'TailRecurseCall', 'args, kwargs')
def tco(f):
def wrapper(*args, **kwargs):
fr = sys._getframe()
b = (fr.f_back and fr.f_back.f_back and
fr.f_back.f_back.f_code == fr.f_code)
del fr
if b:
return TailRecurseCall(args, kwargs)
else:
while 1:
r = f(*args, **kwargs)
if isinstance(r, TailRecurseCall):
args = r.args
kwargs = r.kwargs
else:
return r
return wrapper
trampoline на
стероидах
“правильная”
реализация
36. Итераторы
● В Python они везде!
● Но в Python3K их еще больше.
● Простая универсальная абстракция.
● Ленивые вычисления.
● Простота композиции.
● Запись в “итеративном” стиле
(генераторы)
39. Итераторы не pure
odd = lambda x: bool(x % 2)
odds = ifilter(odd, count())
print(list(islice(odds, 4)))
# [1, 3, 5, 7] – ok
print(list(islice(odds, 4)))
# [9, 11, 13, 15] – WTF
Итераторы работают с побочными эффктами
Можно использовать (пройтись) только один раз.
40. Lazycol
class lazycol(object):
__slots__ = 'iterator'
def __new__(cls, iterable):
if isinstance(iterable, (tuple, frozenset, lazycol)):
return iterable
return object.__new__(cls)
def __init__(self, iterable):
self.iterator = iter(iterable)
def __iter__(self):
self.iterator, result = itertools.tee(self.iterator)
return result
41. Fn - Streams
s = Stream() << [1,2,3,4,5]
assert list(s) == [1,2,3,4,5]
assert s[1] == 2
assert list(s[0:2]) == [1,2]
s = Stream() << range(6) << [6,7]
assert list(s) == [0,1,2,3,4,5,6,7]
def gen():
yield 1; yield 2; yield 3
s = Stream() << gen << (4,5)
assert list(s) == [1,2,3,4,5]
● Элементы
вычисляются лениво
(“по требованию”).
● Элементы
кешируются.
● Коллекция, не
итератор.
● Мутабельная.
42. Fn - Streams
from fn import Stream
from fn.iters import take, drop, map
from operator import add
f = Stream()
fib = f << [0, 1] << map(add, f, drop(1, f))
assert list(take(10, fib)) == [0,1,1,2,3,5,8,13,21,34]
assert fib[20] == 6765
assert fib[20] == 6765
assert list(fib[30:34]) == [832040,1346269,2178309,3524578]
Ленивая бесконечная последовательность чисел фиббоначи
43. Иммутабельные структуры
● Нельзя случайно изменить – меньше
ошибок и времени в дебаггере.
● Могут быть оптимизированы для
многопоточных програм – не в Python.
● Могут разделять общую структуру.
● Встроенные: str, tuple, frozenset.
44. frozenlist, frozendict
def _build_fmethod(parent_method):
def method(self, *args, **kwargs):
if self._frozen: raise TypeError("frozen list")
return parent_method(self, *args, **kwargs)
return method
class frozenlist(list):
def __init__(self, iterable=None):
list.__init__(self, iterable)
self._frozen = False
if __debug__:
for mn in ['append', 'extend', …, '__delitem__']:
locals()[mn] = _build_fmethod(getattr(list, mn))
def freeze(self):
self._frozen = True
45. Иммутабельные структуры – реализации
● immutablepy
● dictproxyhack
● fronzendict
● changeless
● werkzeug.datastructures
● sqlalchemy.util
48. I have never considered Python to be
heavily influenced by functional
languages, no matter what people say or
think. I was much more familiar with
imperative languages such as C and Algol
68 and although I had made functions
first-class objects, I didn't view Python as
a functional programming language.
Мнение автора языка
Guido van Rossum
49. В завершение
● Python – не функциональный язык.
● Но в нем есть функциональные элементы.
● Избегайте побочных эффектов.
● Не стоит увлекаться классами.
● Плохой код можно написать в любом стиле.
● В любом случае весьма полезно познакомится
с Clojure, Haskell, Erlang и другими.
50. Minsk Python Meetup
Всем большое
спасибо за внимание
вопросы?
a.zhlobich@gmail.com
anjensan@jabber.ru
anjensan at github, habrahabr etc