Есть ли издержки при вложении функций в Python? - PullRequest
56 голосов
/ 20 октября 2011

В Python, если у меня есть дочерняя функция в родительской функции, «инициализируется» (создается) дочерняя функция каждый раз, когда вызывается родительская функция?Есть ли издержки, связанные с вложением функции в другую?

Ответы [ 6 ]

37 голосов
/ 20 октября 2011

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

Резюме: это не бесплатно.

>>> from dis import dis
>>> def foo():
        def bar():
                pass
        return bar

>>> dis(foo)
  2           0 LOAD_CONST               1 (<code object bar at 0x1017e2b30, file "<pyshell#5>", line 2>)
              3 MAKE_FUNCTION            0
              6 STORE_FAST               0 (bar)

  4           9 LOAD_FAST                0 (bar)
             12 RETURN_VALUE 
37 голосов
/ 20 октября 2011

Да, новый объект будет создаваться каждый раз.Это, вероятно, не проблема, если у вас нет в узком кругу.Профилирование сообщит вам, если это проблема.

In [80]: def foo():
   ....:     def bar():
   ....:         pass
   ....:     return bar
   ....: 

In [81]: id(foo())
Out[81]: 29654024

In [82]: id(foo())
Out[82]: 29651384
14 голосов
/ 01 июня 2012

Воздействие есть, но в большинстве случаев оно настолько мало, что вам не о чем беспокоиться - большинство нетривиальных приложений, вероятно, уже имеют узкие места в производительности, воздействие которых на несколько порядков больше, чем это. Вместо этого беспокойтесь о читаемости и возможности повторного использования кода.

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

import gc
from datetime import datetime

class StopWatch:
     def __init__(self, name):
         self.name = name

     def __enter__(self):
         gc.collect()
         self.start = datetime.now()

     def __exit__(self, type, value, traceback):
         elapsed = datetime.now()-self.start
         print '** Test "%s" took %s **' % (self.name, elapsed)

def foo():
     def bar():
          pass
     return bar

def bar2():
    pass

def foo2():
    return bar2

num_iterations = 1000000

with StopWatch('FunctionDefinedEachTime') as sw:
    result_foo = [foo() for i in range(num_iterations)]

with StopWatch('FunctionDefinedOnce') as sw:
    result_foo2 = [foo2() for i in range(num_iterations)]

Когда я запускаю это в Python 2.7 на моем Macbook Air под управлением OS X Lion, я получаю:

** Test "FunctionDefinedEachTime" took 0:00:01.138531 **
** Test "FunctionDefinedOnce" took 0:00:00.270347 **
3 голосов
/ 04 мая 2018

Мне тоже было любопытно, поэтому я решил выяснить , сколько накладных расходов это понесло.TL; DR, ответ не очень.

Python 3.5.2 (default, Nov 23 2017, 16:37:01) 
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from timeit import timeit
>>> def subfunc():
...     pass
... 
>>> def no_inner():
...     return subfunc()
... 
>>> def with_inner():
...     def s():
...         pass
...     return s()
... 
>>> timeit('[no_inner() for _ in range(1000000)]', setup='from __main__     import no_inner', number=1)
0.22971350199986773
>>> timeit('[with_inner() for _ in range(1000000)]', setup='from __main__ import with_inner', number=1)
0.2847519510000893

Мой инстинкт был смотреть на проценты (with_inner медленнее на 24%), но это число вводит в заблуждение в этом случае, так как мы никогда не будем простовернуть значение внутренней функции из внешней функции, особенно с функциями, которые на самом деле ничего не делают.
После этой ошибки я решил сравнить ее с другими общими вещами, чтобы увидеть, когда это имеет значение и не имеет значения:

    >>> def no_inner():
    ...     a = {}
    ...     return subfunc()
    ... 
    >>> timeit('[no_inner() for _ in range(1000000)]', setup='from __main__ import no_inner', number=1)
    0.3099582109998664

Глядя на это, мы видим, что это занимает меньше времени, чем создание пустого дикта ( быстрый способ ), поэтому, если вы делаете что-то нетривиальное,это, вероятно, не имеет значения вообще.

1 голос
/ 24 июня 2014

Другие ответы великолепны и действительно хорошо отвечают на вопрос.Я хотел добавить, что в Python можно избежать большинства внутренних функций, используя циклы for, генерируя функции и т. Д.

Рассмотрим следующий пример:

def foo():
    # I need to execute some function on two sets of arguments:
    argSet1 = (arg1, arg2, arg3, arg4)
    argSet2 = (arg1, arg2, arg3, arg4)

    # A Function could be executed on each set of args
    def bar(arg1, arg2, arg3, arg4):
        return (arg1 + arg2 + arg3 + arg4)

    total =  bar(argSet1)
    total += bar(argSet2)

    # Or a loop could be used on the argument sets
    total = 0
    for arg1, arg2, arg3, arg4 in [argSet1, argSet2]:
        total += arg1 + arg2 + arg3 + arg4

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

0 голосов
/ 17 января 2019

Да. Это позволяет замыкания, а также фабрики функций.

Закрытие заставляет внутреннюю функцию запоминать состояние своего окружения при вызове.

def generate_power(number):

    # Define the inner function ...
    def nth_power(power):
        return number ** power

    return nth_power

Пример

>>> raise_two = generate_power(2)
>>> raise_three = generate_power(3)

>>> print(raise_two(3))
8
>>> print(raise_three(5))
243
"""
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...