Цепочка функций в 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 позволит мне динамически создавать функции, которые мне кажутся очень интересными. Я искал в интернете в течение последнего часа, но не смог найти зацепку в правильном направлении. Так как я не знаю, как это программирование концепция называется, однако, это не может быть слишком удивительно.

как вы называете эту концепцию и где я могу прочитать больше об этом?

5 74

5 ответов:

я не знаю, является ли это функции цепочка столько, сколько это вызвать цепочка, но, так как функции are callables я думаю, что нет никакого вреда. В любом случае, есть два способа, которыми я могу думать об этом:

суб-причислять int и определения __call__:

первый способ будет с таможней int подкласс, который определяет __call__ который возвращает новый экземпляр с обновлено значение:

class CustomInt(int):
    def __call__(self, v):
        return CustomInt(self + v)

функции add теперь можно определить, чтобы вернуть a 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.для более сложных операций, однако, вы должны определить другие dunders соответствующим образом.

как @Caridorc отметил в a комментарий add также может быть просто написано как:

add = CustomInt 

переименование класса в add вместо CustomInt также работает аналогично.


определите закрытие, требуется дополнительный вызов для получения значения:

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

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) который, если a val поставляется, увеличивает его (_inner_adder += val) и если нет, возвращает значение, как это. Как я уже упоминал, это требует дополнительного () вызов для возврата увеличенного значения:

>>> add(1)(2)()
3
>>> add(1)(2)(3)()  # and so on..
6

вы можете ненавидеть меня, но вот один-лайнер :)

add = lambda v: type("", (int,), {"__call__": lambda self, v: self.__class__(self + v)})(v)

Edit: хорошо, как это работает? Код идентичен ответу @Jim, но все происходит в одной строке.

  1. type может использоваться для построения новых типов:type(name, bases, dict) -> a new type. Ибо name мы предоставляем пустую строку, так как имя действительно не нужно в этом случае. Ибо bases (кортеж) мы предлагаем (int,), что идентично наследованию int. dict являются атрибутами класса, куда мы прикрепляем элемент __call__ лямда.
  2. self.__class__(self + v) идентичен return CustomInt(self + v)
  3. новый тип строится и возвращается в пределах внешней лямбды.

Если вы хотите определить функцию, которая будет вызываться несколько раз, сначала вам нужно каждый раз возвращать вызываемый объект (например, функцию), иначе вам нужно создать свой собственный объект, определив __call__ атрибут, чтобы он был вызываемым.

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

вот пример функции, использующей сопрограммы, которая сохраняет последнее состояние себя. Обратите внимание, что он не может быть вызван несколько раз, так как возвращаемое значение является 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

питонский способ сделать это будет использовать динамические аргументы:

def add(*args):
    return sum(args)

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

просто:

class add(int):
   def __call__(self, n):
      return add(self + n)