Набір функцій у Python


90

На 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 дозволить мені динамічно створювати функції, що здається мені дуже цікавим. Протягом останньої години я шукав в Інтернеті, але не зміг знайти провід у правильному напрямку. Оскільки я не знаю, як називається ця концепція програмування, проте, це може не надто дивувати.

Як ви називаєте це поняття і де я можу більше про нього прочитати?


7
Схоже, ви шукаєте функції каррі
OneCricketeer

2
Підказка: Вкладена функція динамічно створюється, має доступ до локальних даних батьківської функції та може бути повернута як (викликається) об'єкт.
Джонатан Рейнхарт

@JonathonReinhart Ось так я думав про проблему. Але я насправді не бачив, як це реалізувати.
Stefan Mesken

3
Окрім того: Python однозначно дозволить вам динамічно створювати функції. Якщо вам цікаво, ось кілька супутніх понять, на які слід ознайомитись: WP: Першокласні функції | Як зробити функцію вищого порядку в Python? | functools.partial()| WP: Закриття
Лукас Граф,

@LukasGraf Я його подивлюсь. Дякую!
Stefan Mesken

Відповіді:


100

Я не знаю, чи є це ланцюжком функцій настільки, наскільки це викликається ланцюгом, але, оскільки функції є викликами, я думаю, шкоди не завдано. У будь-якому випадку, я можу придумати це зробити двома способами:

Підкласифікація 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__поведінку ints. Однак для більш складних операцій слід визначити інші дундери відповідним чином .

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

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

6
В інтерактивному коді add = CostumIntтеж має працювати і бути простішим.
Карідорк

4
Проблема підкласифікації вбудованих програм полягає (2*add(1)(2))(3)в TypeErrorтому, що не вдається виконати програму a, оскільки intвона не викликається. В основному CustomInt, перетворюється на звичайний intпри використанні в будь-якому контексті, крім виклику. Для більш надійного рішення вам в основному доведеться повторно впровадити всі __*__методи, включаючи __r*__версії ...
Bakuriu

@Caridorc Або взагалі не називайте це, CustomIntале addпри визначенні.
minipif

27

Ви можете мене ненавидіти, але ось однокласник :)

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

Редагувати: Добре, як це працює? Код ідентичний відповіді @Jim, але все відбувається в одному рядку.

  1. typeможе бути використано для побудови нових типів: type(name, bases, dict) -> a new type. Адже nameми надаємо порожній рядок, оскільки ім’я в цьому випадку насправді не потрібне. Для bases(кортежу) ми пропонуємо an (int,), який ідентичний успадкуванню int. dict- це атрибути класу, де ми прикріплюємо __call__лямбда.
  2. self.__class__(self + v) ідентично return CustomInt(self + v)
  3. Новий тип конструюється і повертається всередині зовнішньої лямбди.

17
Або навіть коротше:class add(int):__call__ = lambda self, v: add(self+v)
Бакуріу

3
Код усередині класу виконується точно так само, як і звичайний код, тому ви можете визначити спеціальні методи за призначенням. Єдина відмінність полягає в тому, що область дії класу трохи ... своєрідна.
Бакуріу

16

Якщо ви хочете визначити функцію, яку потрібно викликати кілька разів, спочатку потрібно кожного разу повертати об’єкт, що викликається (наприклад, функцію), інакше вам потрібно створити власний об’єкт, визначивши __call__атрибут, щоб він викликався.

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

Ось зразок функції з використанням 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

6

Пітонічним способом зробити це було б використання динамічних аргументів:

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

Це не відповідь, яку ви шукаєте, і ви можете це знати, але я думав, що все одно дам, бо якби хтось замислювався про те, щоб робити це не з цікавості, а заради роботи. Ймовірно, у них повинна бути відповідь "правильно вчинити".


1
Я видалив вашу примітку " PS ", нікочар. Ми всі усвідомлюємо, наскільки елегантний Python :-) Я не думаю, що він належить у тілі відповіді.
Dimitris Fasarakis Hilliard

6
Думаю, ти міг би просто зробити, add = sumякби
збирався


3

Якщо ви бажаєте прийняти додаткове ()для отримання результату, ви можете використовувати 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- це шлях.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.