¿Python reutiliza los resultados de cálculos repetidos?
interpreter interpreted-language (4)
Si tengo una expresión que deseo evaluar en Python, como la expresión para
r
en el fragmento de código a continuación, ¿el intérprete de Python será inteligente y reutilizará el resultado secundario
x+y+z
, o simplemente lo evaluará dos veces?
También me interesaría saber si la respuesta a esta pregunta sería la misma para un lenguaje compilado, por ejemplo, C.
x = 1
y = 2
z = 3
r = (x+y+z+1) + (x+y+z+2)
Si tengo una expresión que deseo evaluar en Python, como la expresión para
r
en el fragmento de código a continuación, ¿el intérprete de Python será inteligente y reutilizará el resultado secundariox+y+z
, o simplemente lo evaluará dos veces?
¿De qué intérprete de Python estás hablando? Actualmente hay cuatro implementaciones estables de Python listas para producción en uso generalizado. Ninguno de ellos tiene un intérprete de Python, cada uno de ellos compila Python.
Algunos de ellos pueden o no pueden realizar esta optimización para al menos algunos programas en al menos algunas circunstancias.
La especificación del lenguaje Python no requiere ni prohíbe este tipo de optimización, por lo que cualquier implementación de Python que cumpla con las especificaciones podría, pero no es obligatorio, realizar esta optimización.
Estoy bastante seguro de que, al contrario de todas las otras respuestas que afirman que Python no puede hacer esto, PyPy es capaz de realizar esta optimización. Además, dependiendo de la plataforma subyacente que use, el código ejecutado usando Jython o IronPython también se beneficiará de esta optimización, por ejemplo, estoy 100% seguro de que el compilador C2 de Oracle HotSpot realiza esta optimización.
También me interesaría saber si la respuesta a esta pregunta sería la misma para un lenguaje compilado [...].
No existe un "lenguaje compilado". La compilación y la interpretación son rasgos del compilador o intérprete (¡duh!), No del lenguaje. Cada idioma puede ser implementado por un compilador, y cada idioma puede ser implementado por un intérprete. Caso en cuestión: hay intérpretes para C y, por el contrario, todas las implementaciones de Python, ECMAScript, Ruby y PHP existentes actualmente listas para producción, estables y ampliamente utilizadas tienen al menos un compilador, muchos incluso tienen más de uno (por ejemplo, PyPy , V8, SpiderMonkey, Squirrelfish Extreme, Chakra).
Un lenguaje es un conjunto abstracto de reglas y restricciones matemáticas escritas en una hoja de papel. Un idioma no se compila ni se interpreta, un idioma simplemente lo es . Esos conceptos viven en diferentes capas de abstracción; si el inglés fuera un idioma escrito, el término "idioma compilado" sería un error de tipo.
También me interesaría saber si la respuesta a esta pregunta sería la misma para […] ej. C.
Hay muchas implementaciones de C estables listas para producción en uso generalizado. Algunos de ellos pueden o no pueden realizar esta optimización para al menos algunos programas en al menos algunas circunstancias.
La especificación del lenguaje C no requiere ni prohíbe este tipo de optimización, por lo que cualquier implementación de C que cumpla con las especificaciones podría, pero no es obligatorio, realizar esta optimización.
No, Python no hace eso por defecto.
Si necesita Python para preservar el resultado de un determinado cálculo, debe decirle a Python que haga eso, una forma de hacerlo sería definiendo una función y utilizando los
functools.lru_cache
functools.lru_cache
:
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)
Puede verificar eso con
dis.dis
.
El resultado es:
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
Por lo tanto, no almacenará en caché el resultado de la expresión entre paréntesis.
Aunque para ese caso específico sería posible, en general no lo es, ya que las clases personalizadas pueden definir
__add__
(o cualquier otra operación binaria) para modificarse.
Por ejemplo:
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
Si tiene una función costosa de la que desea almacenar en caché el resultado, puede hacerlo a través de
functools.lru_cache
por ejemplo.
Por otro lado, el compilador realizará un plegado constante como se puede ver en los siguientes ejemplos:
>>> 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
EDITAR: Esta respuesta se aplica solo al intérprete CPython predeterminado del lenguaje Python. Es posible que no sea aplicable a otras implementaciones de Python que adopten técnicas de compilación JIT o que utilicen un sublenguaje restringido de Python que permita la inferencia de tipos estáticos. Consulte la respuesta de @ Jörg W Mittag para obtener más detalles.
No, no lo hará. Puede hacer esto para ver el código compilado:
from dis import dis
dis("r=(x+y+z+1) + (x+y+z+2)")
Salida:
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
Esto se debe en parte a que Python se escribe dinámicamente.
Por lo tanto, los tipos de variables no se conocen fácilmente en tiempo de compilación.
Y el compilador no tiene forma de saber si el operador
+
, que puede sobrecargarse por clases de usuario, podría tener algún efecto secundario.
Considere el siguiente ejemplo sencillo:
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)
Para expresiones simples, puede guardar los resultados intermedios en una nueva variable:
z = x + y + 1
r = z + (z + 1)
Para llamadas a funciones,
functools.lru_cache
es otra opción, como ya lo indican otras respuestas.