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

50
Pyton – пробуем функциональный стиль Жлобич Андрей Wargaming.net Python Developer Minsk Python Meetup

Upload: python-meetup

Post on 01-Jul-2015

1.793 views

Category:

Technology


3 download

DESCRIPTION

Pyton – пробуем функциональный стиль Автор: Андрей Жлобич (Wargaming)

TRANSCRIPT

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

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

стиль

Жлобич АндрейWargaming.net

Python Developer

Minsk Python Meetup

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

Принципы ФП

✔ Чистота – нет побочных эффектов.✔ Функции – сущности 1го рода.✔ Функции высших порядков.✔ Замыкания.✔ Рекурсия.✔ Неизменяемые структуры.✔ Ленивые вычисления.

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

Versus

➔ Императивный стиль

➔ Функциональный Декларативный стиль

grep 'search-for' inputfile.txt > s1.txt sort s1.txt > s2.txtuniq s2.txt > result.txtrm s1.txt s2.txt

cat inputfile.txt | grep 'search-for' | sort | uniq | cat > result.txt

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

Чистые функции

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

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

Побочные эффекты – плохо?

● Могут приводить к гейзенбагам.

● Без побочных эффектов никак не обойтись.

● Даже в pure-functional языках.

● Но их можно локализовать.

● Система типов может в этом помогать.

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

Ошибки начинающих

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?`

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

Мутабельный объект

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

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

Немутабельный объект

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

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

А зачем тогда объект?

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

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

“Перестаньте писать классы”

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')

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

Высокоуровневые функции ввода-вывода

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)

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

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): ...

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

Функции – сущности 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

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

singledispatch – тест

CPython PyPy0

10

20

30

40

50

60

70

80

90

class singledispatch

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

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

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

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

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

Origins of lambda

April 2001 – Python 2.1 – замыкания

December 2008 – Python 3.0

● Больше итераторов (map, filter, dict values/keys).

● Хотели убрать lambda – оставили!

● Добавили nonlocal – мутабельные замыкания.

● Убрали reduce из builtins.

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

Sugared lambda

Добавляем “новый” синтаксис

from underscore import _

print map(_ + 1, [1, 2, 3])

assert (_)(1) == 1

assert (_ + _)(1, 2) == 3

assert ((_ * _) + _)(2, 3, 4) == 10

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

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): ... ...

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

Sugared lambda – скорость

CPython PyPy0

5

10

15

20

25

30

35

40

45

50

lambda underscore whatever fn

X 4

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

Функции высших порядков

Функции принимают другие функции в качестве аргументов.

map, filter, timeit, iter...

Все декораторы!

trace, memoize, locking, transaction...

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

Пример

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)

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

Функции – тоже данные

● Можно хранить в структурах данных.

● Список или словарь функций – хорошо!

● Не забываем про замыкания.

● Функции – атомарные значения.

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

Функции везде

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

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

(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"]

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

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))

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

Рекурсия

● Во многих функциональных языках – единственная операция для огранизации цикла.

● Многие алгоритмы проще выражаются в рамках рекурсии.

● Не типична для Python программ.

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

Хвостовая рекурсия

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

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

TCO в Python

● Мешает красивым стектрейсам.

● Это оптимизация – деталь реализации, а не элемент языка.

● Рекурсия – не базовая операция в программировании.

● Мешает динамичная сущность Python'а.

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

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))

Эмулируем хвостовую рекурсию

Нельзя использовать как декоратор!

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

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

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

Сами реализуем TCO

➔ Модификация байткода.➔ Модификация исходного кода (препроцессинг).➔ Анализ стека (sys._getframe()).➔ Хранение локального состояния (threading.local).

Оптимизируем хвостовой вызов без модификации самой функции

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

Используем 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

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

Используем 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 на стероидах

“правильная” реализация

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

Trampoline/TCO bench

CPython PyPy0

5

10

15

20

25

30

35

40

loop recursive trampoline tco-simple tco-getframe

X 5

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

Итераторы

● В Python они везде!● Но в Python3K их еще больше.● Простая универсальная абстракция.● Ленивые вычисления.● Простота композиции.● Запись в “итеративном” стиле

(генераторы)

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

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)]

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

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]]

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

Итераторы не 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

Итераторы работают с побочными эффктамиМожно использовать (пройтись) только один раз.

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

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

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

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]

● Элементы вычисляются лениво (“по требованию”).

● Элементы кешируются.

● Коллекция, не итератор.

● Мутабельная.

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

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]

Ленивая бесконечная последовательность чисел фиббоначи

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

Иммутабельные структуры

● Нельзя случайно изменить – меньше ошибок и времени в дебаггере.

● Могут быть оптимизированы для многопоточных програм – не в Python.

● Могут разделять общую структуру.

● Встроенные: str, tuple, frozenset.

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

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

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

Иммутабельные структуры – реализации

● immutablepy

● dictproxyhack

● fronzendict

● changeless

● werkzeug.datastructures

● sqlalchemy.util

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

Персистентные структуры

● Хрянят “историю”

● Разделяют общую структуру

● Используются в Clojure, Git, CouchDB

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

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)]

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

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

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

В завершение

● Python – не функциональный язык.● Но в нем есть функциональные элементы.● Избегайте побочных эффектов.● Не стоит увлекаться классами.● Плохой код можно написать в любом стиле.● В любом случае весьма полезно познакомится

с Clojure, Haskell, Erlang и другими.

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

Minsk Python Meetup

Всем большоеспасибо за внимание

вопросы?

[email protected]@jabber.ru

anjensan at github, habrahabr etc