Подтвердить что ты не робот

Скомпилированы внутренние функции Python?

Итак, AFAIK в CPython, определения функций скомпилируются в объекты функции при выполнении во время разбора. Но как насчет внутренних функций? Собираются ли они скомпилированы в функциональные объекты во время разбора или они компилируются (или интерпретируются) каждый раз, когда вызывается функция? Влияют ли внутренние функции на любой штраф за исполнение?

4b9b3361

Ответ 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).