Как работают вложенные функции в Python? - PullRequest
53 голосов
/ 05 января 2010
def maker(n):
    def action(x):
        return x ** n
    return action

f = maker(2)
print(f)
print(f(3))
print(f(4))

g = maker(3)
print(g(3))

print(f(3)) # still remembers 2

Почему вложенная функция запоминает первое значение 2, хотя maker() вернулось и вышло к моменту вызова action()?

Ответы [ 9 ]

38 голосов
/ 05 января 2010

Вы в основном создаете замыкание .

В информатике замыкание - это первоклассная функция со свободными переменными, которые связаны в лексической среде. Говорят, что такая функция «замкнута» над своими свободными переменными.

Связанное чтение: Закрытия: почему они так полезны?

Закрытие - это просто более удобный способ дать функции доступ к локальному состоянию.

С http://docs.python.org/reference/compound_stmts.html:

Примечание программиста: функции являются первоклассными объектами. Форма 'def', выполняемая внутри определения функции, определяет локальную функцию, которая может быть возвращена или передана. Свободные переменные, используемые во вложенной функции, могут обращаться к локальным переменным функции, содержащей def. Подробнее см. Раздел «Наименование и привязка».

30 голосов
/ 05 января 2010

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

См. Это как «динамическое создание функции».

def maker(n):
  def action(x):
    return x ** n
  return action

f = maker(2)
--> def action(x):
-->   return x ** 2

Это базовое поведение в Python, оно делает то же самое с несколькими назначениями.

a = 1
b = 2
a, b = b, a

Python читает это как

a, b = 2, 1

Он в основном вставляет значения, прежде чем что-либо делать с ними.

14 голосов
/ 05 января 2010

Вы определяете две функции. Когда вы звоните

f = maker(2)

вы определяете функцию, которая возвращает двойное число, поэтому

f(2) --> 4
f(3) --> 6

Затем вы определяете ДРУГУЮ РАЗНУЮ ФУНКЦИЮ

g = maker(3)

, которые возвращают три раза число

g(3) ---> 9

Но это две разные функции, это не одна и та же функция, каждая из которых независима. Даже в области видимости внутри функции 'maker' вызываются одинаково, это не одна и та же функция, каждый раз, когда вы вызываете maker(), вы определяете другую функцию. Это как локальная переменная, каждый раз, когда вы вызываете функцию, она принимает одно и то же имя, но может содержать разные значения. В этом случае переменная 'action' содержит функцию (которая может быть другой)

9 голосов
/ 05 января 2010

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

В вашем примере вложенная функция action использует переменную n, поэтому она формирует замыкание вокруг этой переменной и запоминает ее для последующих вызовов функций.

3 голосов
/ 07 сентября 2015

Давайте рассмотрим три распространенные причины написания внутренних функций.

1. Затворы и заводские функции

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

def print_msg(msg):
    """This is the outer enclosing function"""

    def printer():
        """This is the nested function"""
        print(msg)

    return printer  # this got changed

Теперь давайте попробуем вызвать эту функцию.

>>> another = print_msg("Hello")
>>> another()
Hello

Это необычно. Функция print_msg() была вызвана со строкой "Hello", а возвращаемая функция была связана с именем another. При вызове another() сообщение все еще запоминалось, хотя мы уже завершили выполнение функции print_msg(). Этот метод, с помощью которого некоторые данные ("Hello") привязываются к коду, в Python называется замыканием.

Когда использовать закрытие?

Так, для чего хороши затворы? Замыкания могут избежать использования глобальных значений и предоставляют некоторую форму сокрытия данных. Он также может предоставить объектно-ориентированное решение проблемы. Когда в классе реализовано мало методов (в большинстве случаев один метод), замыкания могут обеспечить альтернативные и более элегантные решения. Reference

2. Инкапсуляция:

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

3. Keepin 'it DRY

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

def process(file_name):
    def do_stuff(file_process):
        for line in file_process:
            print(line)
    if isinstance(file_name, str):
        with open(file_name, 'r') as f:
            do_stuff(f)
    else:
        do_stuff(file_name)

Более подробно вы можете обратиться к этому блогу.

2 голосов
/ 05 января 2010

Потому что во время создания функции n было 2, поэтому ваша функция:

def action(x):
    return x ** 2

Когда вы вызываете f (3), x устанавливается на 3, поэтому ваша функция вернет 3 ** 2

1 голос
/ 13 февраля 2017

Одно из применений - вернуть функцию, которая поддерживает параметр.

def outer_closure(a):
    #  parm = a               <- saving a here isn't needed
    def inner_closure():
        #return parm
        return a              # <- a is remembered 
    return inner_closure

# set parm to 5 and return address of inner_closure function
x5 = outer_closure(5)
x5()
>5

x6 = outer_closure(6)
x6()
>6

# x5 inner closure function instance of parm persists 
x5()
>5
1 голос
/ 05 января 2010

Люди правильно ответили о закрытии, то есть: действительное значение для «n» внутри действия - это последнее значение, которое оно имело при вызове «maker».

Один простой способ преодолеть это - сделать ваш freevar (n) переменной внутри функции «action», которая получает копию «n» в момент запуска:

Самый простой способ сделать это - установить «n» в качестве параметра, значение по умолчанию которого «n» на момент создания. Это значение для «n» остается фиксированным, поскольку параметры по умолчанию для функции хранятся в кортеже, который является атрибутом самой функции (в данном случае action.func_defaults):

def maker(n):
    def action(x, k=n):
        return x ** k
    return action

Использование:

f = maker(2) # f is action(x, k=2)
f(3)   # returns 3^2 = 9
f(3,3) # returns 3^3 = 27
0 голосов
/ 05 января 2010

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

Когда вы вызываете его во второй раз, вы создаете второй объект функции. Таким образом, f указывает на первый функциональный объект (квадрат-значение), а g указывает на второй функциональный объект (куб-значение). Когда Python видит «f (3)», это означает «выполнить объект функции, указанный как переменную f, и передать ему значение 3». f и g и различные функциональные объекты и, таким образом, возвращают разные значения.

...