Итак, AFAIK в CPython, определения функций скомпилируются в объекты функции при выполнении во время разбора. Но как насчет внутренних функций? Собираются ли они скомпилированы в функциональные объекты во время разбора или они компилируются (или интерпретируются) каждый раз, когда вызывается функция? Влияют ли внутренние функции на любой штраф за исполнение?
Скомпилированы внутренние функции Python?
Ответ 1
Чтобы дать общее объяснение - если у вас есть следующий код в модуле:
def outer(x=1):
def inner(y=2):
return x+y
Когда файл разбирается с помощью python через compile()
, приведенный выше текст превращается в байт-код для выполнения модуля. В байт-коде модуля есть два "объекта кода", один для байт-кода outer()
и один для байт-кода inner()
. Обратите внимание, что я сказал объекты кода, а не функции - объекты кода содержат немного больше, чем байт-код, используемый функцией, и любую информацию, которая может быть известна во время компиляции - например, байт-код для outer()
, содержащий ссылку на байт-код для inner()
.
Когда модуль действительно загружается, оценивая объект кода, связанный с модулем, происходит одна вещь: фактический "объект функции" создается для outer()
и сохраняется в атрибуте outer
модуля. Объект функции действует как коллекция байт-кода и всех контекстно-зависимых вещей, которые необходимы для вызова функции (например, какие глобальные переменные требуют от нее и т.д.), Которые не могут быть известны во время компиляции. В некотором смысле объект кода является шаблоном для функции, которая является шаблоном для выполнения фактического байт-кода со всеми заполненными переменными.
Ни одна из них не связана с inner()
-a-a-function еще. - Каждый раз, когда вы действительно обходите вызов outer()
, это когда создается новый объект функции inner()
для этого вызова внешнего, который связывает уже созданный внутренний объект байт-кода в список глобалов, включая значение x
, переданное в этот вызов внешнему. Как вы можете себе представить, это довольно быстро, так как не требуется синтаксический анализ, просто заполняя быструю структуру некоторыми указателями на другие уже существующие объекты.
Ответ 2
>>> import dis
>>> def foo():
... def bar():
... print "stuff"
... return bar
...
>>> b = foo()
>>> dis.dis(foo)
2 0 LOAD_CONST 1 (<code object bar at 0x20bf738, file "<stdin>", line 2>)
3 MAKE_FUNCTION 0
6 STORE_FAST 0 (bar)
4 9 LOAD_FAST 0 (bar)
12 RETURN_VALUE
>>> dis.dis(b)
3 0 LOAD_CONST 1 ('stuff')
3 PRINT_ITEM
4 PRINT_NEWLINE
5 LOAD_CONST 0 (None)
8 RETURN_VALUE
Я подозреваю, что это сильно зависит от реализации, но это был CPython 2.6.6, и внутренняя функция выглядит так, как будто она была скомпилирована. Вот еще один пример:
>>> def foo():
... def bar():
... return 1
... return dis.dis(bar)
...
>>> foo()
3 0 LOAD_CONST 1 (1)
3 RETURN_VALUE
Итак, мы можем заключить, что они скомпилированы. Что касается их рабочих характеристик, используйте их. Если у вас возникли проблемы с производительностью, профиль. Я знаю, что это не совсем ответ, но это почти никогда не имеет значения, и когда это происходит, общие ответы не режут. Функциональные вызовы несут некоторые накладные расходы, и похоже, что внутренние функции - это как функции.
Ответ 3
Простой тест: аргументы по умолчанию для функции вызывают один раз, в определенное время.
>>> def foo():
... def bar(arg=count()):
... pass
... pass
...
>>> def count():
... print "defined"
...
>>> foo()
defined
>>> foo()
defined
Итак, да: это незначительный (очень-очень маленький) удар производительности.
Ответ 4
Чтобы расширить внутреннюю функцию nmichaels, компилируются во время компиляции, как он догадывался, и в байтах foo.func_code.co_consts
сохраняется байтовый код, и к ним обращаются с помощью кода операции LOAD_CONST, как вы можете видеть при разборке функции.
Пример:
>>> def foo():
... def inner():
... pass
>>> print foo.func_code.co_consts
(None, <code object inner at 0x249c6c0, file "<ipython console>", line 2>)
Ответ 5
Я опаздываю на это, но, как небольшое экспериментальное дополнение к этим исчерпывающим ответам: вы можете использовать встроенную функцию id
для убедитесь, что новый объект создан или нет:
In []: # inner version
def foo():
def bar():
return id(bar)
return bar()
foo(), foo()
Out[]: (4352951432, 4352952752)
Фактические числа могут отличаться, но их отличие указывает на то, что действительно созданы два разных экземпляра bar
.
In []: # outer version
def bar():
return id(bar)
def foo():
return bar()
foo(), foo()
Out[]: (4352950952, 4352950952)
На этот раз, как и ожидалось, два id
совпадают.
Теперь для некоторых измерений timeit
. Внутренний первый, внешний второй:
100000 loops, best of 3: 1.93 µs per loop
1000000 loops, best of 3: 1.25 µs per loop
Итак, на моей машине кажется, что внутренняя версия на 50% медленнее (Python 2.7, IPython Notebook).