Время создания для изменяемых аргументов замыканий по умолчанию в Python

Насколько я понимаю, когда Python анализирует исходный код функции, он компилирует его в байт-код, но не запускает этот байт-код до вызова функции (поэтому недопустимые имена переменных в функциях не вызывают исключение, если вы не вызываете функцию ).

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

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

def f(x):
    def g(y, a=[]):
        a.append(y)
        return a

    for y in range(x, x + 2):
        print('calling g from f:', g(y))
    return g(y + 1)

for x in range(2):
    print('calling f from module scope:', f(x))

Это распечатывает

calling g from f: [0]
calling g from f: [0, 1]
calling f from module scope: [0, 1, 2]
calling g from f: [1]
calling g from f: [1, 2]
calling f from module scope: [1, 2, 3]

Означает ли это, что каждый раз, когда вызывается f, байт-код g перестраивается? Такое поведение кажется ненужным и странным, поскольку байт-код f (включая g?) создается только один раз. Или, возможно, это только аргумент по умолчанию g, который повторно создается при каждом вызове f?


person jmd_dk    schedule 19.12.2016    source источник
comment
Ваше понимание неверно. Определение функции — это исполняемый код, который Python запускает всякий раз, когда вызывается область, в которой он находится. Для функции уровня модуля это происходит при импорте модуля; для вложенной функции это происходит при вызове внешней функции. В любом случае в этот момент создаются аргументы по умолчанию.   -  person Daniel Roseman    schedule 20.12.2016
comment
Отлично, это возвращает симметрию. Внутренняя функция относится к внешней функции так же, как внешняя функция к модулю.   -  person jmd_dk    schedule 20.12.2016
comment
Байт-код не перестраивается каждый раз, объект кода используется повторно, как указано в этом ответе.   -  person Ilja Everilä    schedule 20.12.2016


Ответы (3)


Первое заблуждение: «когда Python анализирует исходный код функции, он компилирует его в байт-код, но не запускает этот байт-код до вызова функции (поэтому недопустимые имена переменных в функциях не вызывают исключение, если вы не вызываете функцию )". Чтобы было ясно, ваше заблуждение заключается в том, что «недопустимые имена переменных в функциях не вызывают исключение, если вы не вызываете функцию». Неназначенные имена не будут перехвачены до тех пор, пока функция не будет выполнена.

Проверьте этот простой тест:

In [1]: def g(a):
   ...:     123onetwothree = a
  File "<ipython-input-5-48a83ac30c7b>", line 2
    123onetwothree = a

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

In [7]: def f(x=[]):
   ...:     print(x)
   ...:     x.append(1)
   ...:     print(x)
   ...:
   ...:

In [8]: f.__defaults__
Out[8]: ([],)

In [9]: f()
[]
[1]

In [10]: f.__defaults__
Out[10]: ([1],)

In [11]:

Что касается вашего примера, каждый раз, когда вы запускаете f, аргумент по умолчанию восстанавливается, потому что вы определяете g внутри f. Лучший способ думать об этом — думать об операторе def как о конструкторе для function объектов и аргументах по умолчанию, таких как параметры для этого конструктора. Каждый раз, когда вы запускаете def some_function, это похоже на вызов конструктора снова и снова, и функция переопределяется, как если бы в теле f было записано g = function(a=[]).

В ответ на комментарий

In [11]: def f(x=h()): pass
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-11-ecbb4a9f8673> in <module>()
----> 1 def f(x=h()): pass

NameError: name 'h' is not defined
person juanpa.arrivillaga    schedule 19.12.2016
comment
Я не куплюсь на ваш пример. 123onetwothree — это синтаксическая ошибка, означающая, что из нее нельзя создать байт-код. Попробуйте какой-нибудь синтаксически допустимый код, например. b = c вместо этого, где c не существует. - person jmd_dk; 20.12.2016
comment
Что касается моего второго заблуждения: попробуйте def f(x=h()), где h не существует. Опять же, перед вызовом функции не генерируется никаких исключений. - person jmd_dk; 20.12.2016
comment
Я думал, что это то, что вы имели в виду под illegal variable names in functions does not throw an exception unless you call the function. - person juanpa.arrivillaga; 20.12.2016
comment
@jmd_dk Конечно, перед вызовом функции возникает исключение. Посмотреть редактирование - person juanpa.arrivillaga; 20.12.2016
comment
Ах, ты прав. Однако он не генерирует исключение, если вы делаете это во внутренней функции. - person jmd_dk; 20.12.2016
comment
@jmd_dk за ваше второе заблуждение: Значения по умолчанию оцениваются в точка определения функции в области определения, .... Обратите внимание на оценку в точке определения функции: если это произойдет внутри другой функции, вы не получите исключение для def f(x=h()), например, до тех пор, пока вы не вызовете внешнюю функцию или, другими словами, не оцените ее тело. - person Ilja Everilä; 20.12.2016

Внутренняя функция перестраивается с использованием существующего байт-кода для внутренней функции. Это легко увидеть, используя dis.

>>> import dis
>>> def make_func():
...     def my_func():
...         pass
...     return my_func
>>> dis.dis(make_func.__code__)
  3       0 LOAD_CONST               1 (<code object my_func at [...]", line 3>)
          3 MAKE_FUNCTION            0
          6 STORE_FAST               0 (my_func)

  5       9 LOAD_FAST                0 (my_func)
         12 RETURN_VALUE

Теперь, если вы сделаете:

>>> f1 = make_func()
>>> f2 = make_func()
>>> f1 is f2
False
>>> f1.__code__ is f2.__code__
True
person Shain    schedule 19.12.2016

Просто посмотрите на байт-код для f с dis:

dis(f)
  2           0 BUILD_LIST               0
              3 LOAD_CONST               1 (<code object g at 0x7febd88411e0, file "<ipython-input-21-f2ef9ebb6765>", line 2>)
              6 LOAD_CONST               2 ('f.<locals>.g')
              9 MAKE_FUNCTION            1
             12 STORE_FAST               1 (g)

  6          15 SETUP_LOOP              46 (to 64)
             18 LOAD_GLOBAL              0 (range)
             21 LOAD_FAST                0 (x)
             24 LOAD_FAST                0 (x)
             27 LOAD_CONST               3 (2)
             30 BINARY_ADD
             31 CALL_FUNCTION            2 (2 positional, 0 keyword pair)
             34 GET_ITER
        >>   35 FOR_ITER                25 (to 63)
             38 STORE_FAST               2 (y)

(обрезано для краткости)

Объект кода, загруженный для g:

3 LOAD_CONST               1 (<code object g at 0x7febd88411e0, file "<ipython-input-21-f2ef9ebb6765>", line 2>)

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

dis(f.__code__.co_consts[1])
  3           0 LOAD_FAST                1 (a)
              3 LOAD_ATTR                0 (append)
              6 LOAD_FAST                0 (y)
              9 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             12 POP_TOP

  4          13 LOAD_FAST                1 (a)
             16 RETURN_VALUE

Каждый раз, когда вызывается f, вызывается MAKE_FUNCTION, который заново создает функцию из уже существующего байтового кода.

person Dimitris Fasarakis Hilliard    schedule 19.12.2016
comment
Но зачем проектировать язык для этого воссоздания? Конечно, это плохо для производительности? - person jmd_dk; 20.12.2016
comment
@jmd_dk, но что, если свободная переменная влияет на то, какая внутренняя функция определена? Это общий шаблон с замыканиями (например, с фабриками функций), и функция должна быть воссоздана. Его предположим нужно воссоздать. - person juanpa.arrivillaga; 20.12.2016
comment
Истинный. Таким образом, в основном функция очищается, когда она выходит за пределы области действия, независимо от того, является ли эта область действия другой функцией или модулем, как и любая другая переменная. Это действительно имеет смысл. - person jmd_dk; 20.12.2016
comment
@jmd_dk Я не совсем понимаю, что вы имеете в виду, но функция очищается, как и любой другой объект: когда счетчик ссылок становится равным 0. - person juanpa.arrivillaga; 20.12.2016
comment
Объект кода для g содержится внутри кода f (ссылка, которая поддерживает его в рабочем состоянии), функция, созданная с помощью MAKE_FUNCTION (объект type function очищается, когда выполнение f завершается как локальная переменная. - person Dimitris Fasarakis Hilliard; 20.12.2016