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

Частичная эффективность 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?

4b9b3361

Ответ 1

Почему вызовы частичных функций занимают больше времени?

Код с partial занимает примерно в два раза больше из-за вызова дополнительной функции. Функциональные вызовы стоят дорого:

Накладные расходы функций в Python относительно высоки, особенно по сравнению со скоростью выполнения встроенной функции.

-

Является ли частичная функция просто перенаправлением параметров исходной функции или она отображает статические аргументы?

Насколько я знаю - да, просто переводит аргументы в исходную функцию.

-

А также существует ли функция в Python, чтобы вернуть тело функции, заполненной, учитывая, что все параметры предопределены, например, с помощью функции i?

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

Вот прототип:

import timeit
import types


# http://stackoverflow.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

Ответ 2

Вызовы функции с частично применяемыми аргументами дороже, потому что вы удваиваете количество вызовов функций. Эффект 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