Русский
Русский
English
Статистика
Реклама

Пайплайны и частичное применения функций, зачем это в Python


Одно из главных достоинств Python его выразительность. Функциональные средства языка позволяют лаконично описывать преобразования над данными. На мой взгляд в Python не хватает некоторых инструментов, которые помогли бы удобнее описывать преобразования данных и дополнить функциональную составляющую языка, в частности "пайплайны функций" и их частичное применение. Поэтому в этом посте я лью воду о возможности и необходимости данных средств с экспериментами по их реализации. Пришёл во многом за критикой. Приятного чтения!


Кратко о ФП в Python и почему не хватает пайплайнов на примере


В Python из базовых средств есть довольно удобные map(), reduce(), filter(), лямбда-функции, итераторы и генераторы. Малознакомым с этим всем советую данную статью. В целом это оно всё позволяет быстро и естественно описывать преобразования над списками, кортежами, и тд. Очень часто(у меня и знакомых питонистов) то, что получается однострочник по сути набор последовательных преобразований, фильтраций, например:
Kata с CodeWars: Найти


$\forall n \in [a,b] : n=\sum_0^{len(n)} n_i ^ i, \text{ } n_i\text{ - i-й разряд числа n}$


Задачка довольно простая, к сожалению(но к счастью для этого поста), решений лучше чем в лоб нет.


Моё решение:


def sum_dig_pow(a, b): # range(a, b + 1) will be studied by the function    powered_sum = lambda x: sum([v**(i+1) for i,v in enumerate(map(lambda x: int(x), list(str(x))))])    return [i for i in range(a,b+1) if powered_sum(i)==i]

С использованием средств ФП как есть получается скобочный ад "изнутри наружу". Это мог бы исправить пайплайн.


Пайплайны функций


Под сим я подразумеваю такое в идеальном случае (оператор "|" личное предпочтение):


# f3(f2(f1(x)))f1 | f2 | f3 >> xpipeline = f1 | f2 | f3 pipeline(x)pipeline2 = f4 | f5pipeline3 = pipeline | pipeline2 | f6...

Тогда powered_sum может стать(код не рабочий):


powered_sum = str | list | map(lambda x: int(x), *args) | enumerate | [v**(i+1) for i,v in *args] | sum

Как по мне, такой код легче писать и читать. args в целом выглядят чужеродно. В реальности, не лазя в кишки питона получилось сделать такое(далеко от любых идеалов):


from copy import deepcopyclass CreatePipeline:    def __init__(self, data=None):        self.stack = []        if data is not None:            self.args = data    def __or__(self, f):        new = deepcopy(self)        new.stack.append(f)        return new    def __rshift__(self, v):        new = deepcopy(self)        new.args = v        return new    def call_logic(self, *args):        for f in self.stack:            if type(args) is tuple:                args = f(*args)            else:                args = f(args)        return args    def __call__(self, *args):        if 'args' in self.__dict__:            return self.call_logic(self.args)        else:            return self.call_logic(*args)

Естественно, это один большой костыль, состряпанный ради интереса, даже без kwargs, хотя в похожих случаях и не так важно.


pipe = CreatePipeline()powered_sum = pipe | str | list | (lambda l: map(lambda x: int(x), l)) | enumerate | (lambda e: [v**(i+1) for i,v in e]) | sum

В целом, эта штука даже сейчас уже выглядит имхо лучше, хотя там где остаются преобразования, можно вынести в отдельную функцию, возвращающую функцию от одного аргумента, а здесь мы уже стоит рассмотреть вопрос добавления частичного применения функций.


Частичное применение функций


Рассмотрим на примере простейшей функции(код не рабочий):


def f_partitial (x,y,z):    return x+y+zv = f_partial(1,2)# type(v) = что-нибудь частично применённая функция f_partial, оставшиеся аргументы: ['z']print(v(3))# Эквивалентprint(f_partial(1,2,3))

Такая возможность была бы полезна для пайпа и другого разного(насколько фантазии хватит). Тогда пример с учётом имеющейся реализации pipe может стать таким:


powered_sum = pipe | str | list | map(lambda x: int(x)) | enumerate | (lambda e: [v**(i+1) for i,v in e]) | sum# map будет вызван ещё раз со вторым аргументом# map(lambda x: int(x))(данные) при вызове

map(lambda x: int(x)) в пайплайне выглядит более лаконично в целом и в терминах последовательных преобразований данных.
Кривенькая неполная реализация на уровне языка:


from inspect import getfullargspecfrom copy import deepcopyclass CreatePartFunction:    def __init__(self, f):        self.f = f        self.values = []    def __call__(self, *args):        args_f = getfullargspec(self.f)[0]        if len(args) + len(self.values) < len(args_f):            new = deepcopy(self)            new.values = new.values + list(args)            return new        elif len(self.values) + len(args) == len(args_f):            return self.f(*tuple(self.values + list(args)))

Реализация примера с учётом данного костыля дополнения:


# костыль для обхода поломки inspect над встроенным mapm = lambda f, l: map(f, l)# создаём частично применяемую функцию на основе обычной питоньейpmap = CreatePartFunction(m)powered_sum = pipe | str | list | pmap(lambda x: int(x)) | enumerate | (lambda e: [v**(i+1) for i,v in e]) | sum

При более чем двойном вызове в строке(что в целом не особо нужно), придётся уже расставлять скобки, потому что питон подумает, что вызывается аргумент, то есть:


def f (x,y,z):    return x+y+zf = CreatePartFunction(f)# работаетprint(f(1,2,3))# работаетprint(f(1,2)(3))print(f(1)(2,3))# не работает# 2(3) - int не callableprint(f(1)(2)(3))# работаетprint((f(1)(2))(3))

Итоги


Как мне кажется, данные возможности не помешали бы языку из коробки, с, может быть, подобным синтаксисом, они вроде ничего не ломают, но могут сделать последовательности подобных преобразований, функциональные выражения удобнее.

Источник: habr.com
К списку статей
Опубликовано: 04.09.2020 14:06:16
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Ненормальное программирование

Python

Кодобред

Функциональное программирование

Эксперимент

Фп

Пайплайн

Частичное применение

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru