Функция закрытия функции - PullRequest
       11

Функция закрытия функции

8 голосов
/ 02 апреля 2012

Я думал, что я улучшаю производительность, когда заменяю этот код:

def f(a, b):
  return math.sqrt(a) * b
result = []
a = 100
for b in range(1000000):
  result.append(f(a, b))

на:

def g(a):
  def f(b):
    return math.sqrt(a) * b
  return f
result = []
a = 100
func = g(a)
for b in range(1000000):
  result.append(func(b))

Я предполагал, что, поскольку a фиксируется при выполнении закрытия,интерпретатор будет предварительно вычислять все, что включает a, и поэтому math.sqrt(a) будет повторяться только один раз вместо 1000000 раз.

Является ли мое понимание всегда правильным, или всегда неправильным, или правильным / неправильным в зависимости от реализации?

Я заметил, что объект кода для func создается (по крайней мере, в CPython) до времени выполнения и является неизменным.Затем объект кода использует глобальную среду для достижения закрытия.Кажется, это говорит о том, что оптимизация, на которую я надеялся, не произойдет.

Ответы [ 3 ]

13 голосов
/ 02 апреля 2012

Я предполагал, что, так как a фиксируется, когда выполняется замыкание, интерпретатор будет предварительно вычислять все, что включает в себя, и так math.sqrt (a) будет повторяться только один раз вместо 1000000 раз.

Это предположение неверно, я не знаю, откуда оно пришло. Замыкание просто фиксирует привязки переменных, в вашем случае оно фиксирует значение a, но это не означает, что происходит больше магии: выражение math.sqrt(a) по-прежнему вычисляется каждый раз, когда вызывается f.

В конце концов, он должен вычислять каждый раз, потому что интерпретатор не знает, что sqrt является "чистым" (возвращаемое значение зависит только от аргумента и никаких побочных эффектов выполнила). Оптимизации, подобные ожидаемым, практичны в функциональных языках (здесь очень помогают ссылочная прозрачность и статическая типизация), но их будет очень сложно реализовать в Python, который является императивным и динамически типизированным языком.

Тем не менее, если вы хотите предварительно вычислить значение math.sqrt(a), вам нужно сделать это явно:

def g(a):
  s = math.sqrt(a)
  def f(b):
    return s * b
  return f

Или используя lambda:

def g(a): 
  s = math.sqrt(a)
  return lambda b: s * b

Теперь, когда g действительно возвращает функцию с 1 параметром, вы должны вызвать результат только с одним аргументом.

3 голосов
/ 02 апреля 2012

Код не оценивается статически; код внутри функции все еще вычисляется каждый раз. Объект функции содержит весь байт-код, который выражает код в функции; это не оценивает ничего из этого. Вы можете улучшить ситуацию, рассчитав дорогую стоимость один раз:

def g(a):
    root_a = math.sqrt(a)
    def f(b):
        return root_a * b
    return f
result = []
a = 100
func = g(a)
for b in range(1000000):
    result.append(func(b))

Естественно, в этом тривиальном примере вы могли бы значительно улучшить производительность:

a = 100
root_a = math.sqrt(a)
result = [root_a * b for b in range(1000000)]

Но я полагаю, вы работаете с более сложным примером, чем тот, который не масштабируется?

1 голос
/ 02 апреля 2012

Как обычно, модуль timeit - ваш друг. Попробуйте кое-что и посмотрите, как это происходит. Если вас не волнует написание уродливого кода, это также может немного помочь:

def g(a):
   def f(b,_local_func=math.sqrt):
      return _local_func(a)*b

Очевидно, что python снижает производительность при попытке доступа к «глобальной» переменной / функции. Если вы можете сделать этот доступ локальным, вы можете сбрить немного времени.

...