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

Почему 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.

4b9b3361

Ответ 1

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

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

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


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

Здесь мы задумали процесс, который я выполнил для этого.

  • Я попробовал это на своем 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
    

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

  • Я попробовал это на 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 - это то, что вы ожидаете ( "load i, load (2,3), test for membership, return result" ), а то, что вы получили в Python 2.7, - тарабарщина. Ясно, что dis декомпилирует строку как байт-код в 2.7, но компилирует ее как Python в 3.2.

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

    Python 2.7:

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

    Python 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>
    

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

Ответ 2

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

import compiler, dis

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

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

Ответ 3

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

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