Комментарии 14
Пайпы выглядят полезно.
Частичные фунцкии вроде же можно делать так:
def f_partitial (x,y,z):
return x+y+z
my_partial_f = lambda z: f_partial(1, 2, z)
my_partial_f(3) # same as: f_partial(1, 2, 3)
Или я что-то не так понял?
Да, конечно, их так и принято делать, но было бы на мой взгляд здорово иметь возможность сразу писать без лямбды:
def f (x,y,z):
return x+y+z
my_pf = f(1,2)
my_pf(3)
Во первых это снизит читаемость из за неявного изменения поведению только по отсуствию параметра. Что если я забыл его передать? Или параметры со значением по умолчанию? Или *args?
Например:
def f (x, y, z=0):
return x+y+z
Во вторых как быть с необязательными параметрами функции? Мне кажется как есть достаточно.
Явное лучше неявного.
Там есть:
— Типизированные пайплайны: returns.readthedocs.io/en/latest/pages/pipeline.html
— Типизированный `partial` и `curry`: returns.readthedocs.io/en/latest/pages/curry.html
Я против введения новых элементов в язык. Зачем еще один ад, уже есть C++. Если скучно, можно им заняться.
что пайплайны, что «однострочные» решения совершенно нечитаемы.
(Суб)оптимальным по читабельности будет что-то в духе:
def sum_dig_pow(a, b): # range(a, b + 1) will be studied by the function
def powered_sum(x):
digits = [int(symbol) for symbol in str(x)]
return sum([v**(i+1) for i, v in enumerate(digits)])
return [i for i in range(a, b + 1) if powered_sum(i) == i]
Далее — некий псевдокод, вдохновленный соседним data science — языком:
range(a, b+1) |
keep(x ->
x |
str |
map(int) |
map2((v, i) -> v **(i+1)) |
sum |
y -> eq(y, x))
Я уверен, что можно отформатировать и сделать еще более читаемым.
Собственно это мало чем отличается от предложенного автором, смысл же заключается в том, что выражение (а это именно выражение) читается слева-направо, сверху-вниз — и именно в этом порядке происходит обработка данных. В вашем примере приходится несколько раз переключатся между разными строками чтобы понять что происходит.
Неподготовленный человек мыслит более декларативно. И читабельный код — он читабельный для человека. А как человек описал бы алгоритм?
1. Возьмем цифры числа. Цифры — это целочесленные значения (int) символов (symbol) цифр в строке (in str(x)).
2. Далее возьмем сумму (sum) степеней цифр (v ^ (i + 1)) от позиции в строке (enumerate(digits)).
3. Постановка задачи: найти те числа (i) в интервале от a до b (range(1, b+1)), для которых сумма степеней цифр равна самому числу (powered_sum(i) == i).
Как видите, предложеная процедурная запись полностью совпадает со структурой русского языка. Поэтому это читабельно.
Врочем, моя позиция в том, что я верю, что Вам вполне так же удобно в парадигме функционального программирования, как мне в литературного программирования.
Почему это f(1)(2)(3)
не должно работать?
По синтаксису питона, это всё разлагается на f.__call__(1).__call__(2).__call__(3)
, всё левоассоциативное, лишние скобки не нужны.
И кстати, не нужны классы-шмаклассы. Вложенные функции отлично справляются.
def curry(f):
arity = len(getfullargspec(f)[0])
# для простоты, забьём на функции с переменным числом аргументов!!!
def make(*bound):
def partial(*args):
combo = bound + args
assert len(combo) <= arity
if len(combo) == arity:
return f(*combo)
else:
return make(*combo)
# для простоты, забьём на создание документации
return partial
return make()
Однострочники в стиле питона, на list (generator) comprehensions, более-менее отлично читаются, просто незачем их в одну строку пихать.
def solve_that(a, b):
return (
n # сразу ответ, что делаем: возвращаем n
for n in range(a, b+1) # в заданном интервале
if n == sum( # отвечающие условию
di ** i
for i, ci in enumerate(str(n))
for di in [int(ci)] # лайфхак, присваивание внутри for-выражения
# ну, или можно было выше написать int(ci) ** i
)
)
Ну и раз зашла пьянка про конвееры, то чуть компактнее (и без deepcopy)
class PipeValue:
def __init__(self, data):
self.data = data
def __or__(self, next_step):
return PipeValue(next_step(self.data))
class PipeFun:
def __init__(self, steps = []):
self.steps = steps
def __or__(self, next_step):
return PipeFun(self.steps + [next_step])
def __call__(self, data):
return reduce(lambda d, step: step(d), self.steps, data)
def __rrshift__(self, data):
return PipeValue(self(data))
def maps(f):
return lambda series: map(f, series)
(PipeValue('hello') | list | maps(ord) | sum).data
(PipeFun() | list | maps(ord) | sum)('hello')
('hello' >> PipeFun() | list | maps(ord) | sum).data
('hello' >> (PipeFun() | list | maps(ord) | sum)).data
Пайплайны и частичное применения функций, зачем это в Python