Pyton – пробуем
функциональный
стиль
Жлобич Андрей
Wargaming.net
Python Developer
Minsk Python Meetup
Принципы ФП
✔ Чистота – нет побочных эффектов.
✔ Функции – сущности 1го рода.
✔ Функции высших порядков.
✔ Замыкания.
✔ Рекурсия.
✔ Неизменяемые структуры.
✔ Ленивые вычисления.
Versus
➔ Императивный стиль
➔ Функциональный Декларативный стиль
grep 'search-for' inputfile.txt > s1.txt
sort s1.txt > s2.txt
uniq s2.txt > result.txt
rm s1.txt s2.txt
cat inputfile.txt | grep 'search-for' |
sort | uniq | cat > result.txt
Чистые функции
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
Побочные эффекты – плохо?
● Могут приводить к гейзенбагам.
● Без побочных эффектов никак не обойтись.
● Даже в pure-functional языках.
● Но их можно локализовать.
● Система типов может в этом помогать.
Ошибки начинающих
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?
`
Мутабельный объект
class Point(object):
def __init__(self, x, y):
self.x, self.y = x, y
def move(self, dx, dy):
self.x += dx
self.y += dy
def distance(self, other):
return ((self.x - other.x) ** 2 +
(self.y - other.y) ** 2) ** 0.5
Немутабельный объект
class Point(object):
def __init__(self, x, y):
self.x, self.y = x, y
def move(self, dx, dy):
return Point(self.x + dx, self.y + dy)
def distance(self, other):
return ((self.x - other.x) ** 2 +
(self.y - other.y) ** 2) ** 0.5
А зачем тогда объект?
Point = namedtuple('Point', 'x, y')
def move_point(point, dx, dy):
return Point(point.x + dx, point.y + dy)
def distance(point1, point2):
return ((point1.x - point2.x) ** 2 +
(point1.y - point2.y) ** 2) ** 0.5
“Перестаньте писать классы”
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')
Высокоуровневые функции ввода-вывода
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)
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): ...
Функции – сущности 1го рода
def singledispatch(func):
registry = {}
def dispatch(cls):
try:
return registry[cls]
except KeyError:
return _find_impl(cls, registry)
def register(cls, func=None):
if func is None:
return lambda f: register(cls, f)
registry[cls] = func
return func
def wrapper(*args, **kwargs):
return dispatch(args[0].__class__)(*args, **kwargs)
registry[object] = func
wrapper.register = register
wrapper.dispatch = dispatch
return wrapper
singledispatch – тест
CPython PyPy
0
10
20
30
40
50
60
70
80
90
class singledispatch
Origins of lambda
def genfunc(args, expr):
exec("def f(" + args + "): return " + expr)
return eval('f')
vals = [1, 2, 3]
newvals = map(genfunc('x', 'x * 2'), vals)
1993 – Python version < 1.0 – нет lambda
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
Origins of lambda
April 2001 – Python 2.1 – замыкания
December 2008 – Python 3.0
● Больше итераторов (map, filter, dict values/keys).
● Хотели убрать lambda – оставили!
● Добавили nonlocal – мутабельные замыкания.
● Убрали reduce из builtins.
Sugared lambda
Добавляем “новый” синтаксис
from underscore import _
print map(_ + 1, [1, 2, 3])
assert (_)(1) == 1
assert (_ + _)(1, 2) == 3
assert ((_ * _) + _)(2, 3, 4) == 10
Sugared lambda – реализация
● Прототип underscore <90 строк.
● Реализации на pypi – fn, whatever.
● Нету замыканий, оверхед на создание.
class Underscore(object):
__slots__ = ['_arity', '_call']
def __init__(self, arity, call):
self._arity = arity
self._call = call
def __call__(self, *args):
return self._call(*args)
def __add__(self, value):
...
...
Sugared lambda – скорость
CPython PyPy
0
5
10
15
20
25
30
35
40
45
50
lambda underscore whatever fn
X 4
Функции высших порядков
Функции принимают другие функции в
качестве аргументов.
map, filter, timeit, iter...
Все декораторы!
trace, memoize, locking, transaction...
Пример
def process_file(filename):
with open(filename) as fp:
lines = iter(fp.readline, "")
ints = map(int, lines)
print map(memoize(func), ints)
timed(process_file)(filename)
Функции – тоже данные
● Можно хранить в структурах данных.
● Список или словарь функций – хорошо!
● Не забываем про замыкания.
● Функции – атомарные значения.
Функции везде
map("%s:%s".__mod__, zip("abc", [1, 2, 3]))
# ["a1", "b2", "c3"]
filter(set([1, 3]).__contains__, [2, 3, 4, 5])
# [3]
filter(bool, [1, 0, "", None, 3])
# [1, 3]
reduce(operator.mul, range(1, 5))
# 24
(F(f, a, b) << g << F(p, c))(x) ~~ f(a, b, g(p(c, x)))
from fn import F, _
F(add, 1)(10) # 11
f = F(add, 1) << F(mul, 100)
list(map(f, [0, 1, 2]))
# [1, 101, 201]
list(map(F() << str << (_ ** 2), range(1, 5)))
# ["1", "4", "9", "16"]
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))
Рекурсия
● Во многих функциональных языках –
единственная операция для огранизации
цикла.
● Многие алгоритмы проще выражаются в
рамках рекурсии.
● Не типична для Python программ.
Хвостовая рекурсия
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
TCO в Python
● Мешает красивым стектрейсам.
● Это оптимизация – деталь реализации, а не
элемент языка.
● Рекурсия – не базовая операция в
программировании.
● Мешает динамичная сущность Python'а.
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))
Эмулируем
хвостовую рекурсию
Нельзя использовать
как декоратор!
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
Сами реализуем TCO
➔ Модификация байткода.
➔ Модификация исходного кода (препроцессинг).
➔ Анализ стека (sys._getframe()).
➔ Хранение локального состояния (threading.local).
Оптимизируем хвостовой вызов без
модификации самой функции
Используем 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
Используем 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 на
стероидах
“правильная”
реализация
Trampoline/TCO bench
CPython PyPy
0
5
10
15
20
25
30
35
40
loop recursive trampoline tco-simple tco-getframe
X 5
Итераторы
● В Python они везде!
● Но в Python3K их еще больше.
● Простая универсальная абстракция.
● Ленивые вычисления.
● Простота композиции.
● Запись в “итеративном” стиле
(генераторы)
Itertools
things = [('2009-08-01', 11), ('2009-08-23', 3), ('2009-09-03', 10),
('2009-09-03', 4), ('2009-09-05', 22), ('2009-09-09', 33), ...]
get_date = itemgetter(0)
get_value = itemgetter(1)
filtered1 = dropwhile(lambda x: get_date(x) < '2009-09-01', things)
filtered2 = takewhile(lambda x: get_date(x) < '2009-10-01', things)
grouped_by_date = groupby(filtered2, get_date)
get_total_value = lambda (dt, items): 
(dt, reduce(add, map(get_value, items)))
result = sorted(map(get_total_value, grouped_by_date), key=get_value)
print(list(result))
# [('2009-09-03', 36), ('2009-09-06', 33)]
Funcy
from funcy import *
walk(reversed, {'a': 1, 'b': 2}) # {1: 'a', 2: 'b'}
walk_keys(double, {'a': 1, 'b': 2}) # {'aa': 1, 'bb': 2}
walk_values(inc, {'a': 1, 'b': 2}) # {'a': 2, 'b': 3}
select(even, {1,2,3,10,20}) # {2,10,20}
select(r'^a', ('a','b','ab','ba')) # ('a','ab')
some(even, [1, 2, 5]) # 2
take(4, iterate(double, 1)) # [1, 2, 4, 8]
first(drop(3, count(10))) # 13
split(odd, range(5)) # [[1, 3], [0, 2, 4]]
chunks(2, range(5)) # [[0, 1], [2, 3], [4]]
Итераторы не 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
Итераторы работают с побочными эффктами
Можно использовать (пройтись) только один раз.
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
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]
● Элементы
вычисляются лениво
(“по требованию”).
● Элементы
кешируются.
● Коллекция, не
итератор.
● Мутабельная.
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]
Ленивая бесконечная последовательность чисел фиббоначи
Иммутабельные структуры
● Нельзя случайно изменить – меньше
ошибок и времени в дебаггере.
● Могут быть оптимизированы для
многопоточных програм – не в Python.
● Могут разделять общую структуру.
● Встроенные: str, tuple, frozenset.
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
Иммутабельные структуры – реализации
● immutablepy
● dictproxyhack
● fronzendict
● changeless
● werkzeug.datastructures
● sqlalchemy.util
Персистентные структуры
● Хрянят “историю”
● Разделяют общую
структуру
● Используются в
Clojure, Git, CouchDB
Funktown
v = ImmutableVector([1, 2, 3])
v2 = v.conj(9)
print(v2)
# [1, 2, 3, 9]
v = v.assoc(1, 999)
print(v, v[1])
# 999
v = v.conj([])
v[3].append('BAD')
print(v)
# [1, 2, 3, ['BAD']]
d = ImmutableDict({'a': 1})
d = d.assoc('b', 2)
print(d['b'], d.get('b'))
# 2 2
print(d.assoc('c', 9))
# {'a': 1, 'c': 9, 'b': 2}
print(d.remove('d'))
# {'a': 1, 'b': 2}
print(d.items())
# [('a', 1), ('b', 2)]
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
В завершение
● Python – не функциональный язык.
● Но в нем есть функциональные элементы.
● Избегайте побочных эффектов.
● Не стоит увлекаться классами.
● Плохой код можно написать в любом стиле.
● В любом случае весьма полезно познакомится
с Clojure, Haskell, Erlang и другими.
Minsk Python Meetup
Всем большое
спасибо за внимание
вопросы?
a.zhlobich@gmail.com
anjensan@jabber.ru
anjensan at github, habrahabr etc

Pyton – пробуем функциональный стиль

  • 1.
    Pyton – пробуем функциональный стиль ЖлобичАндрей Wargaming.net Python Developer Minsk Python Meetup
  • 2.
    Принципы ФП ✔ Чистота– нет побочных эффектов. ✔ Функции – сущности 1го рода. ✔ Функции высших порядков. ✔ Замыкания. ✔ Рекурсия. ✔ Неизменяемые структуры. ✔ Ленивые вычисления.
  • 3.
    Versus ➔ Императивный стиль ➔Функциональный Декларативный стиль grep 'search-for' inputfile.txt > s1.txt sort s1.txt > s2.txt uniq s2.txt > result.txt rm s1.txt s2.txt cat inputfile.txt | grep 'search-for' | sort | uniq | cat > result.txt
  • 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? `
  • 7.
    Мутабельный объект class Point(object): def__init__(self, x, y): self.x, self.y = x, y def move(self, dx, dy): self.x += dx self.y += dy def distance(self, other): return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5
  • 8.
    Немутабельный объект class Point(object): def__init__(self, x, y): self.x, self.y = x, y def move(self, dx, dy): return Point(self.x + dx, self.y + dy) def distance(self, other): return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5
  • 9.
    А зачем тогдаобъект? Point = namedtuple('Point', 'x, y') def move_point(point, dx, dy): return Point(point.x + dx, point.y + dy) def distance(point1, point2): return ((point1.x - point2.x) ** 2 + (point1.y - point2.y) ** 2) ** 0.5
  • 10.
    “Перестаньте писать классы” classGreeting(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.
    Высокоуровневые функции ввода-вывода defprocess_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): ...
  • 13.
    Функции – сущности1го рода def singledispatch(func): registry = {} def dispatch(cls): try: return registry[cls] except KeyError: return _find_impl(cls, registry) def register(cls, func=None): if func is None: return lambda f: register(cls, f) registry[cls] = func return func def wrapper(*args, **kwargs): return dispatch(args[0].__class__)(*args, **kwargs) registry[object] = func wrapper.register = register wrapper.dispatch = dispatch return wrapper
  • 14.
    singledispatch – тест CPythonPyPy 0 10 20 30 40 50 60 70 80 90 class singledispatch
  • 15.
    Origins of lambda defgenfunc(args, expr): exec("def f(" + args + "): return " + expr) return eval('f') vals = [1, 2, 3] newvals = map(genfunc('x', 'x * 2'), vals) 1993 – Python version < 1.0 – нет lambda
  • 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 April2001 – Python 2.1 – замыкания December 2008 – Python 3.0 ● Больше итераторов (map, filter, dict values/keys). ● Хотели убрать lambda – оставили! ● Добавили nonlocal – мутабельные замыкания. ● Убрали reduce из builtins.
  • 18.
    Sugared lambda Добавляем “новый”синтаксис from underscore import _ print map(_ + 1, [1, 2, 3]) assert (_)(1) == 1 assert (_ + _)(1, 2) == 3 assert ((_ * _) + _)(2, 3, 4) == 10
  • 19.
    Sugared lambda –реализация ● Прототип underscore <90 строк. ● Реализации на pypi – fn, whatever. ● Нету замыканий, оверхед на создание. class Underscore(object): __slots__ = ['_arity', '_call'] def __init__(self, arity, call): self._arity = arity self._call = call def __call__(self, *args): return self._call(*args) def __add__(self, value): ... ...
  • 20.
    Sugared lambda –скорость CPython PyPy 0 5 10 15 20 25 30 35 40 45 50 lambda underscore whatever fn X 4
  • 21.
    Функции высших порядков Функциипринимают другие функции в качестве аргументов. map, filter, timeit, iter... Все декораторы! trace, memoize, locking, transaction...
  • 22.
    Пример def process_file(filename): with open(filename)as fp: lines = iter(fp.readline, "") ints = map(int, lines) print map(memoize(func), ints) timed(process_file)(filename)
  • 23.
    Функции – тожеданные ● Можно хранить в структурах данных. ● Список или словарь функций – хорошо! ● Не забываем про замыкания. ● Функции – атомарные значения.
  • 24.
    Функции везде map("%s:%s".__mod__, zip("abc",[1, 2, 3])) # ["a1", "b2", "c3"] filter(set([1, 3]).__contains__, [2, 3, 4, 5]) # [3] filter(bool, [1, 0, "", None, 3]) # [1, 3] reduce(operator.mul, range(1, 5)) # 24
  • 25.
    (F(f, a, b)<< g << F(p, c))(x) ~~ f(a, b, g(p(c, x))) from fn import F, _ F(add, 1)(10) # 11 f = F(add, 1) << F(mul, 100) list(map(f, [0, 1, 2])) # [1, 101, 201] list(map(F() << str << (_ ** 2), range(1, 5))) # ["1", "4", "9", "16"]
  • 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): ifn: 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 - варианты defrecur(*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 на стероидах “правильная” реализация
  • 35.
    Trampoline/TCO bench CPython PyPy 0 5 10 15 20 25 30 35 40 looprecursive trampoline tco-simple tco-getframe X 5
  • 36.
    Итераторы ● В Pythonони везде! ● Но в Python3K их еще больше. ● Простая универсальная абстракция. ● Ленивые вычисления. ● Простота композиции. ● Запись в “итеративном” стиле (генераторы)
  • 37.
    Itertools things = [('2009-08-01',11), ('2009-08-23', 3), ('2009-09-03', 10), ('2009-09-03', 4), ('2009-09-05', 22), ('2009-09-09', 33), ...] get_date = itemgetter(0) get_value = itemgetter(1) filtered1 = dropwhile(lambda x: get_date(x) < '2009-09-01', things) filtered2 = takewhile(lambda x: get_date(x) < '2009-10-01', things) grouped_by_date = groupby(filtered2, get_date) get_total_value = lambda (dt, items): (dt, reduce(add, map(get_value, items))) result = sorted(map(get_total_value, grouped_by_date), key=get_value) print(list(result)) # [('2009-09-03', 36), ('2009-09-06', 33)]
  • 38.
    Funcy from funcy import* walk(reversed, {'a': 1, 'b': 2}) # {1: 'a', 2: 'b'} walk_keys(double, {'a': 1, 'b': 2}) # {'aa': 1, 'bb': 2} walk_values(inc, {'a': 1, 'b': 2}) # {'a': 2, 'b': 3} select(even, {1,2,3,10,20}) # {2,10,20} select(r'^a', ('a','b','ab','ba')) # ('a','ab') some(even, [1, 2, 5]) # 2 take(4, iterate(double, 1)) # [1, 2, 4, 8] first(drop(3, count(10))) # 13 split(odd, range(5)) # [[1, 3], [0, 2, 4]] chunks(2, range(5)) # [[0, 1], [2, 3], [4]]
  • 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 fromfn 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): defmethod(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
  • 46.
    Персистентные структуры ● Хрянят“историю” ● Разделяют общую структуру ● Используются в Clojure, Git, CouchDB
  • 47.
    Funktown v = ImmutableVector([1,2, 3]) v2 = v.conj(9) print(v2) # [1, 2, 3, 9] v = v.assoc(1, 999) print(v, v[1]) # 999 v = v.conj([]) v[3].append('BAD') print(v) # [1, 2, 3, ['BAD']] d = ImmutableDict({'a': 1}) d = d.assoc('b', 2) print(d['b'], d.get('b')) # 2 2 print(d.assoc('c', 9)) # {'a': 1, 'c': 9, 'b': 2} print(d.remove('d')) # {'a': 1, 'b': 2} print(d.items()) # [('a', 1), ('b', 2)]
  • 48.
    I have neverconsidered 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