Использует ли Python повторные результаты расчетов? - программирование
Подтвердить что ты не робот

Использует ли Python повторные результаты расчетов?

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

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

Было высказано предположение, что этот вопрос похож на этот вопрос. Я считаю, что это похоже. Тем не менее, я считаю, что связанный вопрос - это не "минимальный пример". Также в связанном вопросе нет двусмысленности относительно порядка операций, например, в примерах, аналогичных этому вопросу, где нет определенного порядка операций (математически), в зависимости от порядка вызовов отдельных функций (которые неоднозначно), может быть сделана худшая или лучшая работа по оптимизации. Рассмотрим (abba) (abba) (baa * b), есть вложенные повторяющиеся подстроки, и в зависимости от порядка и объема предварительной обработки может быть выполнено много разных оптимизаций.

4b9b3361

Ответ 1

Вы можете проверить это с помощью 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

Ответ 2

ОБНОВЛЕНИЕ: Этот ответ применяется только к интерпретатору CPython по умолчанию языка Python. Это может быть неприменимо к другим реализациям Python, которые используют методы JIT-компиляции или используют ограниченный подъязык Python, который допускает вывод статического типа. Подробнее см. @Jörg W Mittag 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 - это еще одна опция, как уже указывалось в других ответах.

Ответ 3

  Если у меня есть выражение, которое я хочу оценить в 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, может, но не обязана, выполнять эту оптимизацию.

Ответ 4

Нет, 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)