Подтвердить что ты не робот

Почему Python оптимизирует "если 0", но не "если нет"?

Почему это происходит, если вы компилируете условное выражение типа

def f():
    if None:
        print(222)
    if 0:
        print(333)

ветки, которые используют числа, оптимизируются, но те, которые используют None, не так ли? Пример:

 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        

В каких сценариях могли бы if 0 и if None вести себя по-другому?

4b9b3361

Ответ 1

Мое предположение: это надзор, который произошел, потому что None является просто специальным именем (или глобальным) в python-2.x.

Если вы посмотрите на код байт-кода-оптимизатора в 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 ... */

}

Вы можете заметить, что None загружается с помощью LOAD_GLOBAL или LOAD_NAME, а затем заменяется на LOAD_CONST.

Однако: после его замены он break s, поэтому он не может попасть в случай LOAD_CONST, в котором блок будет заменен на NOP, если константа не является True.


В python-3.x оптимизатору не нужен специальный случай имя (или глобальное) None, потому что он всегда загружается LOAD_CONST и оптимизатор bytecode читает:

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 ... */

}

Нет никакого особого случая для LOAD_NAME и LOAD_GLOBAL, поэтому if None (но также if False - False также была сделана константа в python-3.x) войдет в случай LOAD_CONST а затем заменить на NOP.

Ответ 2

Отказ от ответственности: на самом деле это не ответ, а просто отчет о моем преуспевшем попытайтесь переопределить None в CPython 2.7, несмотря на защиту с помощью компилятор.

Я нашел способ переопределить None в CPython 2.7, хотя это связано с грязным трюком и аналогичным образом можно было бы сделать с литералами. А именно, я заменяю константу entry # 0 в поле co_consts объекта кода:

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()

Вывод:

None is false
None is true