Цепочка функций в Python
На codewars.com я столкнулся со следующей задачей:
Создайте функцию add
, которая добавляет числа вместе при вызове последовательно. Поэтому add(1)
должен возвращать 1
, add(1)(2)
должен возвращать 1+2
,...
Пока я знаком с основами Python, я никогда не сталкивался с функцией, которая может быть вызвана в такой последовательности, то есть функцией f(x)
, которую можно назвать f(x)(y)(z)...
. До сих пор я даже не уверен, как интерпретировать это обозначение.
Как математик, я подозреваю, что f(x)(y)
- это функция, которая назначает каждой x
функцию g_{x}
, а затем возвращает g_{x}(y)
и аналогично для f(x)(y)(z)
.
Если эта интерпретация будет правильной, Python позволит мне динамически создавать функции, которые мне очень интересны. Я искал в Интернете последний час, но не смог найти лидерство в правильном направлении. Однако, поскольку я не знаю, как называется эта концепция программирования, это может быть не слишком неожиданным.
Как вы называете это понятие и где я могу узнать больше об этом?
Ответы
Ответ 1
Я не знаю, является ли это цепочки функций так же сильно, как и вызываемая цепочка, но поскольку функции являются вызывающими, я думаю, что никакого вреда не было сделано. В любом случае, я могу думать об этом двумя способами:
Подклассификация int
и определение __call__
:
Первым способом будет с пользовательским подклассом int
, который определяет __call__
, который возвращает новый экземпляр самого себя с обновленным значением
class CustomInt(int):
def __call__(self, v):
return CustomInt(self + v)
Функция add
теперь может быть определена для возврата экземпляра CustomInt
, который в качестве вызываемого, который возвращает обновленное значение самого себя, может быть вызван последовательно:
>>> def add(v):
... return CustomInt(v)
>>> add(1)
1
>>> add(1)(2)
3
>>> add(1)(2)(3)(44) # and so on..
50
Кроме того, в качестве подкласса int
возвращаемое значение сохраняет поведение __repr__
и __str__
int
s. Тем не менее, для более сложных операций вы должны определить другие проходы соответствующим образом.
Как отметил @Caridorc в комментарии, add
также можно просто написать как:
add = CustomInt
Переименование класса на add
вместо CustomInt
также работает аналогично.
Определите замыкание, требуется дополнительный вызов для получения значения:
Единственный другой способ, о котором я могу думать, включает вложенную функцию, для которой требуется дополнительный пустой вызов аргумента, чтобы вернуть результат. Я не с помощью nonlocal
и предпочитаю прикреплять атрибуты к объектам функции, чтобы сделать его переносимым между Pythons:
def add(v):
def _inner_adder(val=None):
"""
if val is None we return _inner_adder.v
else we increment and return ourselves
"""
if val is None:
return _inner_adder.v
_inner_adder.v += val
return _inner_adder
_inner_adder.v = v # save value
return _inner_adder
Это непрерывно возвращает себя (_inner_adder
), который, если поставляется val
, увеличивает его (_inner_adder += val
), а если нет, возвращает значение как есть. Как я уже упоминал, для возврата добавочного значения требуется дополнительный вызов ()
:
>>> add(1)(2)()
3
>>> add(1)(2)(3)() # and so on..
6
Ответ 2
Вы можете меня ненавидеть, но вот однострочный:)
add = lambda v: type("", (int,), {"__call__": lambda self, v: self.__class__(self + v)})(v)
Изменить: Хорошо, как это работает? Код идентичен ответу @Jim, но все происходит на одной строке.
-
type
можно использовать для построения новых типов: type(name, bases, dict) -> a new type
. Для name
мы предоставляем пустую строку, так как в этом случае имя не требуется. Для bases
(кортеж) мы предоставляем (int,)
, который идентичен наследованию int
. dict
- атрибуты класса, в которые мы прикрепляем __call__
lambda.
-
self.__class__(self + v)
идентичен return CustomInt(self + v)
- Новый тип создается и возвращается внутри внешней лямбда.
Ответ 3
Если вы хотите определить функцию, которая будет вызываться несколько раз, сначала вам нужно каждый раз возвращать вызываемый объект (например, функцию), иначе вам нужно создать свой собственный объект, указав атрибут __call__
, чтобы для того, чтобы он был доступен для вызова.
Следующий момент заключается в том, что вам нужно сохранить все аргументы, которые в этом случае означают, что вы можете использовать Coroutines или рекурсивную функцию. Но обратите внимание, что Coroutines гораздо более оптимизированы/гибки, чем рекурсивные функции, специально для таких задач.
Вот примерная функция, использующая Coroutines, которая сохраняет самое последнее состояние. Обратите внимание, что его нельзя вызывать несколько раз, поскольку возвращаемое значение является integer
, которое не может быть вызвано, но вы можете подумать об обращении в ожидаемый объект; -).
def add():
current = yield
while True:
value = yield current
current = value + current
it = add()
next(it)
print(it.send(10))
print(it.send(2))
print(it.send(4))
10
12
16
Ответ 4
Питонический способ сделать это - использовать динамические аргументы:
def add(*args):
return sum(args)
Это не тот ответ, который вы ищете, и вы можете это знать, но я думал, что дам его в любом случае, потому что если кто-то задается вопросом об этом не из любопытства, а для работы. Вероятно, у них должно быть "правильное дело".