Почему Python не любит списки?

Почему в Python (2.7.2)

import dis
dis.dis("i in (2, 3)")

работает так, как ожидалось, тогда как

import dis
dis.dis("i in [2, 3]")

поднимает:

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.7/dis.py", line 45, in dis
  disassemble_string(x)
File "/usr/lib/python2.7/dis.py", line 112, in disassemble_string
  labels = findlabels(code)
File "/usr/lib/python2.7/dis.py", line 166, in findlabels
 oparg = ord(code[i]) + ord(code[i+1])*256
IndexError: string index out of range

Обратите внимание, что это не влияет на Python3.


person Inkane    schedule 06.05.2012    source источник
comment
Но dis.dis("a = [2, 3]; i in a") работает нормально   -  person San4ez    schedule 07.05.2012
comment
он работает в питоне 3.2.3   -  person mata    schedule 07.05.2012


Ответы (3)


Краткий ответ

В Python 2.x тип str содержит необработанные байты, поэтому dis предполагает, что если вы передаете ему строку, он получает скомпилированный байт-код. Он пытается дизассемблировать строку, которую вы передаете ей как байт-код, и — исключительно из-за деталей реализации байт-кода Python — преуспевает для i in (2,3). Очевидно, однако, что он возвращает тарабарщину.

В Python 3.x тип str предназначен для строк, а тип bytes — для необработанных байтов, поэтому dis может различать скомпилированный байт-код и строки — и предполагает, что он получает исходный код, если он получает нить.


Длинный ответ

Вот мыслительный процесс, которому я следовал, чтобы решить эту проблему.

  1. Я попробовал это на своем Python (3.2):

    >>> import dis
    >>> dis.dis("i in (2,3)")  
      1           0 LOAD_NAME                0 (i)
                  3 LOAD_CONST               2 ((2, 3))
                  6 COMPARE_OP               6 (in)
                  9 RETURN_VALUE
    >>> dis.dis("i in [2,3]")
      1           0 LOAD_NAME                0 (i)
                  3 LOAD_CONST               2 ((2, 3))
                  6 COMPARE_OP               6 (in)
                  9 RETURN_VALUE
    

    Очевидно, это работает.

  2. Я попробовал это на Python 2.7:

    >>> import dis
    >>> dis.dis("i in (2,3)")
              0 BUILD_MAP       26912
              3 JUMP_FORWARD    10272 (to 10278)
              6 DELETE_SLICE+0
              7 <44>
              8 DELETE_SLICE+1
              9 STORE_SLICE+1
    >>> dis.dis("i in [2,3]")
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "C:\Python27\lib\dis.py", line 45, in dis
        disassemble_string(x)
      File "C:\Python27\lib\dis.py", line 112, in disassemble_string
        labels = findlabels(code)
      File "C:\Python27\lib\dis.py", line 166, in findlabels
        oparg = ord(code[i]) + ord(code[i+1])*256
    IndexError: string index out of range
    

    Ага! Заметьте также, что сгенерированный байт-код в Python 3.2 соответствует вашим ожиданиям («загрузить i, загрузить (2,3), проверить на членство, вернуть результат»), тогда как в Python 2.7 вы получите тарабарщину. Очевидно, что dis декомпилирует строку как байт-код в 2.7, но компилирует ее как Python в 3.2.

  3. Я просмотрел исходный код dis.dis. Вот ключевые моменты:

    Питон 2.7:

    elif isinstance(x, str):
        disassemble_string(x)
    

    Питон 3.2:

       elif isinstance(x, (bytes, bytearray)): # Raw bytecode
           _disassemble_bytes(x)
       elif isinstance(x, str):    # Source code
           _disassemble_str(x)
    

    Ради интереса давайте проверим это, передав те же байты в dis в Python 3:

    >>> dis.dis("i in (2,3)".encode())
              0 BUILD_MAP       26912
              3 JUMP_FORWARD    10272 (to 10278)
              6 <50>
              7 <44>
              8 <51>
              9 <41>
    

    Ага! Тарабарщина! (Хотя обратите внимание, что это немного другая тарабарщина — байт-код изменился с версией Python!)

person Katriel    schedule 06.05.2012
comment
А вот соответствующая часть документа Из документации Python2 ( docs.python.org/library/dis .html ): ›dis.dis([bytesource]) › Дизассемблировать объект источника байтов. › bytesource может обозначать модуль, класс, метод, функцию или объект кода, тогда как в документации Python 3 ( docs.python.org/dev/library/dis.html?highlight=dis#dis) в нем указано: ›dis.dis(x=None) ›Разберите объект x. x может обозначать модуль, класс, метод, функцию, объект кода, строку исходного кода или последовательность байтов необработанного байт-кода. - person Inkane; 07.05.2012

dis.dis ожидает в качестве аргумента байт-код, а не исходный код Python. Хотя ваш первый пример «работает», он не дает никакого осмысленного вывода. Вы, вероятно, хотите:

import compiler, dis

code = compiler.compile("i in [2, 3]", '', 'single')
dis.dis(code)

Это работает, как и ожидалось. (Я тестировал только в 2.7).

person georg    schedule 06.05.2012

Если вы просто пытаетесь получить байт-код для простого выражения, проще всего передать его в dis как лямбду с вашим выражением в качестве тела лямбды:

>>> import dis
>>> dis.dis(lambda i : i in [3,2])
  1           0 LOAD_FAST                0 (i)
              3 LOAD_CONST               2 ((3, 2))
              6 COMPARE_OP               6 (in)
              9 RETURN_VALUE
person PaulMcG    schedule 06.05.2012