На 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 позволит мне динамически создавать функции, что мне кажется очень интересным. Я искал в Интернете последний час, но не смог найти зацепку в правильном направлении. Однако, поскольку я не знаю, как называется эта концепция программирования, это не может быть слишком удивительным.
Как вы называете эту концепцию и где я могу прочитать о ней подробнее?
источник
functools.partial()
| WP: ЗакрытиеОтветы:
Я не знаю, является ли это цепочкой функций в той же степени, что и вызываемой цепочкой, но, поскольку функции являются вызываемыми, я думаю, что это не причинит вреда. В любом случае есть два способа сделать это:
Подклассификация
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__
и s. Однако для более сложных операций вам следует соответствующим образом определить другие dunders .__str__
int
Как отметил @Caridorc в комментарии, это
add
можно также просто записать как:Переименование класса в
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
), который, если задан aval
, увеличивает его (_inner_adder += val
), а если нет, возвращает значение как есть. Как я уже упоминал,()
для возврата увеличенного значения требуется дополнительный вызов:>>> add(1)(2)() 3 >>> add(1)(2)(3)() # and so on.. 6
источник
add = CostumInt
тоже должно работать и быть попроще.(2*add(1)(2))(3)
,TypeError
что ониint
не работают из-за невозможности вызова. В основномCustomInt
преобразуется в простойint
при использовании в любом контексте, кроме вызова. Для более надежного решения вам, по сути, придется заново реализовать все__*__
методы, включая__r*__
версии ...CustomInt
вообще, ноadd
при определении.Вы можете меня ненавидеть, но вот резкость :)
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__
лямбду.self.__class__(self + v)
идентиченreturn CustomInt(self + v)
источник
class add(int):__call__ = lambda self, v: add(self+v)
Если вы хотите определить функцию, которая будет вызываться несколько раз, сначала вам нужно каждый раз возвращать вызываемый объект (например, функцию), в противном случае вам нужно создать свой собственный объект, определив
__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)
Это не тот ответ, который вы ищете, и, возможно, вы это знаете, но я подумал, что все равно дам его, потому что, если бы кто-то задавался вопросом, сделать это не из любопытства, а для работы. Вероятно, у них должен быть правильный ответ.
источник
add = sum
если бы собирались пойти по этому путиПросто:
class add(int): def __call__(self, n): return add(self + n)
источник
Если вы согласны принять дополнительные
()
, чтобы получить результат, вы можете использоватьfunctools.partial
:from functools import partial def add(*args, result=0): return partial(add, result=sum(args)+result) if args else result
Например:
>>> add(1) functools.partial(<function add at 0x7ffbcf3ff430>, result=1) >>> add(1)(2) functools.partial(<function add at 0x7ffbcf3ff430>, result=3) >>> add(1)(2)() 3
Это также позволяет указать сразу несколько номеров:
>>> add(1, 2, 3)(4, 5)(6)() 21
Если вы хотите ограничить его одним номером, вы можете сделать следующее:
def add(x=None, *, result=0): return partial(add, result=x+result) if x is not None else result
Если вы хотите
add(x)(y)(z)
легко вернуть результат и в дальнейшем быть вызываемым, тогда подклассы - это вашint
путь.источник