Цепочка функций в 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)

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