Использует ли Python повторные результаты расчетов? - PullRequest
13 голосов
/ 28 сентября 2019

Если у меня есть выражение, которое я хочу вычислить в Python, например выражение для r в фрагменте кода ниже, будет ли интерпретатор Python разумным и повторно использует подрезультат x+y+z, или просто оценит его дважды?Мне также было бы интересно узнать, будет ли ответ на этот вопрос одинаковым для скомпилированного языка, например C.

x = 1
y = 2
z = 3
r = (x+y+z+1) + (x+y+z+2)

Ответы [ 4 ]

10 голосов
/ 28 сентября 2019

Вы можете проверить это с помощью dis.dis.Вывод:

  2           0 LOAD_CONST               0 (1)
              2 STORE_NAME               0 (x)

  3           4 LOAD_CONST               1 (2)
              6 STORE_NAME               1 (y)

  4           8 LOAD_CONST               2 (3)
             10 STORE_NAME               2 (z)

  5          12 LOAD_NAME                0 (x)
             14 LOAD_NAME                1 (y)
             16 BINARY_ADD
             18 LOAD_NAME                2 (z)
             20 BINARY_ADD
             22 LOAD_CONST               0 (1)
             24 BINARY_ADD
             26 LOAD_NAME                0 (x)
             28 LOAD_NAME                1 (y)
             30 BINARY_ADD
             32 LOAD_NAME                2 (z)
             34 BINARY_ADD
             36 LOAD_CONST               1 (2)
             38 BINARY_ADD
             40 BINARY_ADD
             42 STORE_NAME               3 (r)
             44 LOAD_CONST               3 (None)
             46 RETURN_VALUE

Таким образом, он не будет кэшировать результат выражения в скобках.Хотя для этого конкретного случая это было бы возможно, в общем случае это не так, поскольку пользовательские классы могут определять __add__ (или любую другую двоичную операцию) для изменения самих себя.Например:

class Foo:
    def __init__(self, value):
        self.value = value

    def __add__(self, other):
        self.value += 1
        return self.value + other

x = Foo(1)
y = 2
z = 3
print(x + y + z + 1)  # prints 8
print(x + y + z + 1)  # prints 9

Если у вас есть дорогая функция, для которой вы хотите кэшировать результат, вы можете сделать это, например, с помощью functools.lru_cache.

С другой стороны, компилятор будет выполнять постоянное свертывание , как видно из следующих примеров:

>>> import dis
>>> dis.dis("x = 'abc' * 5")
  1           0 LOAD_CONST               0 ('abcabcabcabcabc')
              2 STORE_NAME               0 (x)
              4 LOAD_CONST               1 (None)
              6 RETURN_VALUE
>>> dis.dis("x = 1 + 2 + 3 + 4")
  1           0 LOAD_CONST               0 (10)
              2 STORE_NAME               0 (x)
              4 LOAD_CONST               1 (None)
              6 RETURN_VALUE
4 голосов
/ 28 сентября 2019

РЕДАКТИРОВАТЬ: Этот ответ применяется только к интерпретатору CPython по умолчанию языка Python.Это может быть неприменимо к другим реализациям Python, которые принимают методы JIT-компиляции или используют ограниченный подъязык Python , который допускает вывод статического типа.См. @ Jörg W Mittag's answer для более подробной информации.

Нет, не будет.Вы можете сделать это, чтобы увидеть скомпилированный код:

from dis import dis
dis("r=(x+y+z+1) + (x+y+z+2)")

Вывод:

          0 LOAD_NAME                0 (x)
          2 LOAD_NAME                1 (y)
          4 BINARY_ADD
          6 LOAD_NAME                2 (z)
          8 BINARY_ADD
         10 LOAD_CONST               0 (1)
         12 BINARY_ADD
         14 LOAD_NAME                0 (x)
         16 LOAD_NAME                1 (y)
         18 BINARY_ADD
         20 LOAD_NAME                2 (z)
         22 BINARY_ADD
         24 LOAD_CONST               1 (2)
         26 BINARY_ADD
         28 BINARY_ADD
         30 STORE_NAME               3 (r)
         32 LOAD_CONST               2 (None)
         34 RETURN_VALUE

Это частично потому, что Python динамически типизирован.Таким образом, типы переменных не легко узнать во время компиляции.И у компилятора нет возможности узнать, может ли оператор +, который может быть перегружен пользовательскими классами, вообще иметь какой-либо побочный эффект.Рассмотрим следующий простой пример:

class A:
    def __init__(self, v):
        self.value = v

    def __add__(self, b):
        print(b)
        return self.value + b

x = A(3)
y = 4
r = (x + y + 1) + (x + y + 2)

Для простых выражений вы можете просто сохранить промежуточные результаты в новой переменной:

z = x + y + 1
r = z + (z + 1)

Для вызовов функций, functools.lru_cache - еще одна опция, как уже указано в других ответах.

2 голосов
/ 29 сентября 2019

Если у меня есть выражение, которое я хочу оценить в Python, такое как выражение для r в фрагменте кода ниже, будет ли интерпретатор Python разумным и повторно использует подрезультат x+y+z, или просто оценитэто дважды?

О каком интерпретаторе Python вы говорите?В настоящее время широко используются четыре готовых стабильных реализации Python.Ни у одного из них на самом деле нет интерпретатора Python, каждый из них компилирует Python.

Некоторые из них могут или не могут выполнить эту оптимизацию по крайней мере для некоторых программ по меньшей мере при некоторых обстоятельствах.

Спецификация языка Python не требует и не запрещает такого рода оптимизацию, поэтому любая реализация Python, соответствующая спецификации, может, но не обязана, выполнять эту оптимизацию.

Я уверен, чтоВ отличие от всех других ответов, в которых говорится, что Python не может этого сделать, PyPy способен выполнить эту оптимизацию.Кроме того, в зависимости от используемой платформы, код, выполненный с использованием Jython или IronPython, также выиграет от этой оптимизации, например, я на 100% уверен, что компилятор C2 Oracle HotSpot выполняет эту оптимизацию.

Мне также было бы интересно узнать, будет ли ответ на этот вопрос одинаковым для скомпилированного языка […].

Не существует такого понятия, как «скомпилированный»язык».Компиляция и интерпретация - это черты компилятора или интерпретатора (дух!), А не языка.Каждый язык может быть реализован компилятором, и каждый язык может быть реализован интерпретатором.Пример: есть интерпретаторы для C, и, наоборот, каждая существующая в настоящее время готовая к работе, стабильная, широко используемая реализация Python, ECMAScript, Ruby и PHP имеет по крайней мере один компилятор, многие даже имеют более одного (например, PyPy, V8, SpiderMonkey, Squirrelfish Extreme, Chakra).

Язык - это абстрактный набор математических правил и ограничений, написанных на листе бумаги.Язык не компилируется и не интерпретируется, просто - это .Эти понятия живут на разных уровнях абстракции;если бы английский был типизированным языком, термин «скомпилированный язык» был бы ошибкой типа.

Мне также было бы интересно узнать, будет ли ответ на этот вопрос одинаковым для […]например, C.

Существует множество готовых к использованию стабильных реализаций языка Си.Некоторые из них могут или не могут выполнить эту оптимизацию по меньшей мере для некоторых программ по меньшей мере при некоторых обстоятельствах.

Спецификация языка C не требует и не запрещает этот вид оптимизации, поэтому любая соответствующая спецификации CРеализация будет разрешено, но не обязательно, для выполнения этой оптимизации.

0 голосов
/ 28 сентября 2019

Нет, python по умолчанию этого не делает.Если вам нужен Python для сохранения результата определенного вычисления, вам нужно неявно сказать Python сделать это, один из способов сделать это - определить функцию и использовать functools.lru_cache docs :

from functools import lru_cache

@lru_cache(maxsize=32)
def add3(x,y,z):
  return x + y + z

x=1
y=2
z=3
r = (add3(x,y,z)+1) + (add3(x,y,z)+2)
...