В Python, если у меня есть дочерняя функция в родительской функции, является ли дочерняя функция "инициализирована" (создана) каждый раз при вызове родительской функции? Есть ли накладные расходы, связанные с вложением функции в другую?
Есть ли накладные расходы при вложенности функций в Python?
Ответ 1
Да, каждый раз будет создан новый объект. Вероятно, это не проблема, если у вас нет жесткой петли. Профилирование скажет вам, если это проблема.
In [80]: def foo():
....: def bar():
....: pass
....: return bar
....:
In [81]: id(foo())
Out[81]: 29654024
In [82]: id(foo())
Out[82]: 29651384
Ответ 2
Объект кода предварительно скомпилирован, так что часть не имеет накладных расходов. Объект функции строится на каждом вызове - он связывает имя функции с объектом кода, записывает переменные по умолчанию и т.д.
Резюме: Это не бесплатно.
>>> 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
Ответ 3
Воздействие, но в большинстве случаев оно настолько мало, что вы не должны беспокоиться об этом - у большинства нетривиальных приложений, вероятно, уже есть узкие места производительности, воздействие которых на несколько порядков больше этого. Вместо этого будьте обеспокоены читабельностью и возможностью повторного использования кода.
Здесь приведен код, который сравнивает производительность переопределения функции каждый раз через цикл для повторного использования предопределенной функции.
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 **
Ответ 4
Мне тоже было любопытно по этому поводу, поэтому я решил выяснить, сколько накладных расходов это понесло. 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
Глядя на это, мы видим, что это занимает меньше времени, чем создание пустого dict (быстрый способ), поэтому, если вы делаете что-то нетривиальное, это, вероятно, не имеет значения вообще.
Ответ 5
Другие ответы велики и действительно хорошо отвечают на вопрос. Я хотел добавить, что большинство внутренних функций можно избежать в python, используя для циклов, генерации функций и т.д.
Рассмотрим следующий пример:
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
Этот пример немного тупой, но я надеюсь, что вы можете видеть мою точку зрения. Внутренние функции часто не нужны.
Ответ 6
Да. Это позволяет замыкания, а также функциональные фабрики.
Закрытие заставляет внутреннюю функцию запоминать состояние своего окружения при вызове.
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
"""