source code python python-2.7 cpython

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 .