code - python download
¿Por qué Python optimiza "si 0", pero no "si ninguno"? (2)
¿Por qué es que si compilas una expresión condicional como
def f():
if None:
print(222)
if 0:
print(333)
Las ramas que usan números se optimizan, pero las que no usan None
¿no? Ejemplo:
3 0 LOAD_CONST 0 (None)
3 POP_JUMP_IF_FALSE 14
4 6 LOAD_CONST 1 (222)
9 PRINT_ITEM
10 PRINT_NEWLINE
11 JUMP_FORWARD 0 (to 14)
5 >> 14 LOAD_CONST 0 (None)
17 RETURN_VALUE
¿En qué escenarios podría if 0
y if None
comportaran de manera diferente?
Descargo de responsabilidad: Esto no es realmente una respuesta, sino solo un informe de mi intento exitoso de anular
None
en CPython 2.7 a pesar de la protección del compilador.
Encontré una forma de anular None
en CPython 2.7, aunque implica un truco sucio y, de manera similar, podría hacerse con literales. A saber, sustituyo la entrada constante # 0 en el campo co_consts
de un objeto de código:
def makeNoneTrueIn(func):
c = func.__code__
func.__code__ = type(c)(c.co_argcount,
c.co_nlocals,
c.co_stacksize,
c.co_flags,
c.co_code,
(True, ) + c.co_consts[1:],
c.co_names,
c.co_varnames,
c.co_filename,
c.co_name,
c.co_firstlineno,
c.co_lnotab,
c.co_freevars,
c.co_cellvars)
def foo():
if None:
print "None is true"
else:
print "None is false"
foo()
makeNoneTrueIn(foo)
foo()
Salida:
None is false
None is true
Mi conjetura: es un descuido que sucedió porque None
es solo un nombre de caja especial (o global) en python-2.x.
Si echas un vistazo al código optimizador de código de bytes en python-2.x :
switch (opcode) {
/* ... More cases ... */
/* Replace LOAD_GLOBAL/LOAD_NAME None
with LOAD_CONST None */
case LOAD_NAME:
case LOAD_GLOBAL:
j = GETARG(codestr, i);
name = PyString_AsString(PyTuple_GET_ITEM(names, j));
if (name == NULL || strcmp(name, "None") != 0)
continue;
for (j=0 ; j < PyList_GET_SIZE(consts) ; j++) {
if (PyList_GET_ITEM(consts, j) == Py_None)
break;
}
if (j == PyList_GET_SIZE(consts)) {
if (PyList_Append(consts, Py_None) == -1)
goto exitError;
}
assert(PyList_GET_ITEM(consts, j) == Py_None);
codestr[i] = LOAD_CONST;
SETARG(codestr, i, j);
cumlc = lastlc + 1;
break; /* Here it breaks, so it can''t fall through into the next case */
/* Skip over LOAD_CONST trueconst
POP_JUMP_IF_FALSE xx. This improves
"while 1" performance. */
case LOAD_CONST:
cumlc = lastlc + 1;
j = GETARG(codestr, i);
if (codestr[i+3] != POP_JUMP_IF_FALSE ||
!ISBASICBLOCK(blocks,i,6) ||
!PyObject_IsTrue(PyList_GET_ITEM(consts, j)))
continue;
memset(codestr+i, NOP, 6);
cumlc = 0;
break;
/* ... More cases ... */
}
Puede observar que None
se carga con LOAD_GLOBAL
o LOAD_NAME
y luego se reemplaza por LOAD_CONST
.
Sin embargo: después de reemplazarlo, break
s, por lo que no puede ir al caso LOAD_CONST
en el que el bloque sería reemplazado por un NOP
si la constante no es True
.
En python-3.x, el optimizador no necesita, en caso especial, el nombre (o global) None
porque siempre está cargado con LOAD_CONST
y el optimizador de LOAD_CONST
dice :
switch (opcode) {
/* ... More cases ... */
/* Skip over LOAD_CONST trueconst
POP_JUMP_IF_FALSE xx. This improves
"while 1" performance. */
case LOAD_CONST:
CONST_STACK_PUSH_OP(i);
if (nextop != POP_JUMP_IF_FALSE ||
!ISBASICBLOCK(blocks, op_start, i + 1) ||
!PyObject_IsTrue(PyList_GET_ITEM(consts, get_arg(codestr, i))))
break;
fill_nops(codestr, op_start, nexti + 1);
CONST_STACK_POP(1);
break;
/* ... More cases ... */
}
Ya no hay un caso especial para LOAD_NAME
y LOAD_GLOBAL
por lo que if None
(pero también if False
- False
también se convirtió en una constante en python-3.x) entrará en el caso LOAD_CONST
y luego LOAD_CONST
reemplazado por un NOP
.