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
Другие вопросы по тегам