Не понимаю внутреннюю функцию в Python

1

У меня есть эта проблема:

cons(a, b) создает пару, а car(pair) и cdr(pair) возвращает первый и последний элементы этой пары. Например, car(cons(3, 4)) возвращает 3, а cdr(cons(3, 4)) возвращает 4.

Учитывая эту реализацию минусов:

def cons(a, b):
    def pair(f):
        return f(a, b)
    return pair

Реализуйте car и cdr.

Я не получаю эту функцию.

Он имеет внутреннюю функцию и вызывает другую функцию в возврате. Насколько я понял, внутренние функции заключаются в том, что эти функции должны зависеть от функций над ними. В этом случае на cons(..).

Но функция не использует a или b. И почему здесь функция f? Задача состоит в том, чтобы реализовать car и cdr, и функция f должна быть предоставлена.

Так может кто-нибудь объяснить мне эту функцию? И как я мог начать с задания?

  • 0
    ИМО, cons должны возвращать кортеж, а не функцию
  • 0
    @ cricket_007: но cons() является частью их назначения. Таким образом, cdr() и car() должны передать функцию, которая принимает первый или последний переданный аргумент.
Показать ещё 2 комментария
Теги:
function
parameters

3 ответа

4

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

>>> def foo():
...     return 'foo was called'
...
>>> foo
<function foo at 0x11b3d8ae8>

Это означает, что вы также можете назначить этот объект другим именам, и вы можете передать их в качестве параметров для вызовов функций. Затем вы можете вызвать объект функции, добавив (...) в ссылку:

>>> bar = foo
>>> bar()
'foo was called'

Имя функции назначается в текущем пространстве имен. В модуле, что глобальные переменные, но в такой функции, как cons, имя добавляется как локальное. return pair в функции cons затем возвращают pair объекта функции вызывающему.

И параметры функции, такие как f и a и b, также являются переменными; если вы передадите объект функции в качестве параметра, тогда parameter_name(...) вызовет paramater_name и передаст любые аргументы в ... f(a, b) вызывает f и переходит в a и b.

Следующим пунктом для понимания являются закрытие; закрытие - дополнительное пространство имен, прикрепленное к объектам функции, для переменных из окружающей области.

В следующем примере spam - это имя в закрытии функции bar:

>>> def foo():
...     spam = 'Vikings'
...     def bar():
...         return spam
...     return bar
...
>>> foo()
<function foo.<locals>.bar at 0x11b44bf28>
>>> foo()()
'Vikings'

Вызов foo() возвращает новый объект функции; функция bar() внутри foo(). Вызов, что возвращаемый объект функции производит 'Vikings', значение spam переменной в функции foo. Как bar() получил к этому доступ? Через закрытие:

>>> foo().__closure__
(<cell at 0x11b3c05b8: str object at 0x11b469180>,)
>>> foo().__closure__[0].cell_contents
'Vikings'

Таким образом, вложенные функции могут иметь доступ к именам из окружающей области через закрытие. (Сторона примечания: это не значение, которое хранится в закрытии, это переменная. Переменная может меняться со временем, и, как и другие переменные, получающие доступ к имени позже, будут отображать новое значение, если впоследствии spam был изменен, вызывающая bar() снова вернет новое значение).

Теперь к вашей функции: cons() создает внутреннюю pair() функций pair(), а pair() имеет доступ к параметрам a и b через ее закрытие:

>>> def cons(a, b):
...     def pair(f):
...         return f(a, b)
...     return pair
...
>>> cons(42, 81)
<function cons.<locals>.pair at 0x11b46f048>
>>> pair_42_81 = cons(42, 81)
>>> pair_42_81.__closure__
(<cell at 0x11b3c02b8: int object at 0x10f59a750>, <cell at 0x11b3c05b8: int object at 0x10f59ac30>)
>>> pair_42_81.__closure__[0].cell_contents
42
>>> pair_42_81.__closure__[1].cell_contents
81

Функция pair() принимает параметр f и вызывает этот параметр, проходящий через a и b. Давайте посмотрим, что произойдет, когда мы перейдем к print.

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

>>> print
<built-in function print>
>>> print('arg1', 'arg2')
arg1 arg2

Если вы передали это функции pair() которая возвращает cons(), вы можете увидеть, что делает f(a, b):

>>> pair_42_81(print)
42 81

print, переданный в pair(), присваивается f, а f(a, b) - это то же самое, что и print(a, b).

Мы видим, что print() вызывается, потому что значения выписываются на консоль. Но вы также можете создать функцию, которая возвращает новое значение. Скажем, у вас есть функция, которая объединяет два числа и возвращает это значение:

>>> def add(first, second):
...     return first + second
...
>>> add(42, 81)
123
>>> pair_42_81(add)
123

Мы можем вызвать функцию напрямую, и 123 возвращается, или мы можем иметь pair_42_81() сделать это для нас, и тот же результат возвращается нам. Просто!

Все это работает, потому что функции являются объектами и могут передаваться как другие переменные, и потому что у pair_42_81 есть a = 42 и c = 81 хранящиеся в закрытии, и будут использовать их для вызова данного объекта f с этими двумя аргументами.

Далее следуют cdr() и car(), которые возвращают либо первый, либо последний элемент пары. Если cons(a, b) создает pair(f) возвращающую f(a, b), то cdr() и car() должны каждый создать функцию для передачи в pair() которая будет извлекать либо a либо b.

Поэтому создайте вложенную функцию в каждой, а также с помощью функции cdr() и car() pair() с этой функцией. Вложенная функция выполняет работу по выбору a или b и возвращает это значение. Затем верните результат вызова во внешний мир:

def car(pair):
    def return_first(a, b):
        return a
    return pair(return_first)

def cdr(pair):
    def return_last(a, b):
        return b
    return pair(return_last)

pair(return_first) вызывает return_first(a, b), a возвращается, и car() может вернуть это вызывающему:

>>> car(cons(42, 81))
42

и то же самое относится к pair(return_last), только теперь возвращается b:

>>> cdr(cons(42, 81))
81

Возможно, вас интересует предыстория этих операций; car, cdr и cons происходят из LISP, где cons ab Создает ячейку с двумя указателями (объясняя имя) и car (что означает, что в состав созданного набора команд IBM 704 LISP было создано содержимое адресной части номера регистра) и cdr (это означает, что часть Сокращающей части номера регистра на языке 704) принимает первую и оставшуюся части такой ячейки. См. Статью в Википедии об именах.

2

Это называется замыканием. Два основных элемента

  • pair функция знает значения a и b. В этом отношении они похожи на локальные переменные
  • функция cons возвращает функцию, а не значение. Вы должны снова вызвать результат

Итак, когда вы вызываете cons(a, b) вы получаете функцию, которая что-то делает для a и b, но пока она еще не знает. Для этого вам нужно передать еще одну функцию. Например

 my_f = cons(1, 2)
 result = my_f(lambda x, y: x + y)
 print(result) # 3

Как это связано с вашим назначением, не очень понятно. Я бы предположил, что для car вам нужен только элемент a (head), а для cdr вы хотите элемент b (остальная часть списка). Таким образом, функции просто вернут один аргумент и игнорируют другой. Например

car_f = lambda x, y: x
cdr_f = lambda x, y: y
  • 1
    Это реализация кортежей в лямбда-исчислении; cons возвращает функциональную кодировку кортежа, где вы извлекаете первый или второй элемент кортежа, вызывая кортеж на соответствующем методе доступа. Это аннулирование обычной реализации, ориентированной на данные. Вы не называете car(some_tuple) ; вместо этого вы вызываете some_tuple(car) .
  • 0
    @chepner: аа, ладно, тогда больше смысла.
1

cons - это функция, которая принимает два аргумента: a и b и возвращает pair функций.

Функция pair принимает функцию f в качестве аргумента, который потребляет два аргумента.

def cons(a, b):
    def pair(f):
        return f(a, b)
    return pair

f = lambda n, m: n**2 + m**3
car = lambda n, m: n
cdr = lambda n, m: m
print(cons(2, 3)(f))
print(cons(2, 3)(car))
print(cons(2, 3)(cdr))

f возвращает 31 = 2**2 + 3**3

Обратите внимание, что cons имеют два раза скобки (...) - один раз, для собственного вызова и в другое время для вызова возвращенных функций.

Обратите внимание на этот ответ, чтобы иметь возможность вызвать car(cons(2, 3)). Вы также можете быть заинтересованы в Почему программа использует закрытие?

  • 0
    Однако в контексте присваивания вы никогда бы не вызвали cons(2,3) для такой функции, как f ; его цель - взять в качестве аргумента car или cdr для получения соответствующего значения из кортежа.
  • 0
    (Вы все еще хотите иметь возможность вызывать car(cons(2,3)) , хотя; car и cdr нужен дополнительный уровень косвенности, чтобы позволить это.)
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню