Python functools частичная эффективность
Я работал с Python и создал следующую ситуацию с кодом:
import timeit
setting = """
import functools
def f(a,b,c):
pass
g = functools.partial(f,c=3)
h = functools.partial(f,b=5,c=3)
i = functools.partial(f,a=4,b=5,c=3)
"""
print timeit.timeit('f(4,5,3)', setup = setting, number=100000)
print timeit.timeit('g(4,5)', setup = setting, number=100000)
print timeit.timeit('h(4)', setup = setting, number=100000)
print timeit.timeit('i()', setup = setting, number=100000)
В результате я получаю следующее:
f: 0.181384086609
g: 0.39066195488
h: 0.425783157349
i: 0.391901016235
Почему вызовы частичных функций занимают больше времени? Частичная функция просто перенаправляет параметры в исходную функцию, или она отображает статические аргументы повсюду? А также, есть ли в Python функция для возврата тела заполненной функции, учитывая, что все параметры предопределены, как с функцией i?
2 ответа
Почему вызовы частичных функций занимают больше времени?
Код с partial
занимает примерно в два раза больше времени из-за дополнительного вызова функции. Функциональные вызовы дороги:
Затраты на вызовы функций в Python относительно высоки, особенно по сравнению со скоростью выполнения встроенной функции.
-
Частичная функция просто перенаправляет параметры в исходную функцию, или она отображает статические аргументы повсюду?
Насколько я знаю - да, он просто перенаправляет аргументы в исходную функцию.
-
А также, есть ли в Python функция для возврата тела заполненной функции, учитывая, что все параметры предопределены, как с функцией i?
Нет, я не знаю такой встроенной функции в Python. Но я думаю, что можно делать то, что вы хотите, так как функции - это объекты, которые можно копировать и изменять.
Вот прототип:
import timeit
import types
# http://stackru.com/questions/6527633/how-can-i-make-a-deepcopy-of-a-function-in-python
def copy_func(f, name=None):
return types.FunctionType(f.func_code, f.func_globals, name or f.func_name,
f.func_defaults, f.func_closure)
def f(a, b, c):
return a + b + c
i = copy_func(f, 'i')
i.func_defaults = (4, 5, 3)
print timeit.timeit('f(4,5,3)', setup = 'from __main__ import f', number=100000)
print timeit.timeit('i()', setup = 'from __main__ import i', number=100000)
который дает:
0.0257439613342
0.0221881866455
Вызовы функции с частично примененными аргументами более дороги, потому что вы удваиваете количество вызовов функции. Эффект functools.partial()
похож на этот пример:
def apply_one_of_two(f, a):
def g(b):
return f(a, b)
return g
Это означает, что apply_one_of_two()
возвращает функцию, и когда она вызывается, это приводит к дополнительному вызову оригинальной функции f
,
Поскольку Python обычно не оптимизирует это, это напрямую приводит к дополнительным усилиям во время выполнения.
Но это не единственный фактор, который следует учитывать в вашем микробенчмарке. При частичных вызовах вы также переключаетесь с позиционных аргументов на ключевые слова, что приводит к дополнительным издержкам.
Когда вы изменяете порядок аргументов в исходной функции, вам не нужны ключевые аргументы при частичных вызовах, и тогда разница во времени выполнения несколько уменьшается, например:
import timeit
setting = """
import functools
def f(a,b,c):
pass
g = functools.partial(f, 4)
h = functools.partial(f, 4, 5)
i = functools.partial(f, 4, 5, 3)
"""
print(timeit.timeit('f(4, 5, 3)', setup = setting, number=100000))
print(timeit.timeit('g(5, 3)', setup = setting, number=100000))
print(timeit.timeit('h(3)', setup = setting, number=100000))
print(timeit.timeit('i()', setup = setting, number=100000))
Вывод (на Intel Skylake i7 под Fedora 27/Python 3.6):
0.010069019044749439
0.01681053702486679
0.018060395028442144
0.011366961000021547