Search code examples
pythonconditional-statementspython-2.5raise

"raise" followed by conditional statement (python)


I'm trying to understand some python 2.5 code and I came across this pattern:

def __init__(self, matrix, top_buttons, side_buttons, config_button):
        raise isinstance(matrix, ButtonMatrixElement) or AssertionError
        raise matrix.width() == 8 and matrix.height() == 8 or AssertionError
        raise isinstance(top_buttons, tuple) or AssertionError
        raise len(top_buttons) == 8 or AssertionError
        raise isinstance(side_buttons, tuple) or AssertionError
        raise len(side_buttons) == 8 or AssertionError
        raise isinstance(config_button, ButtonElement) or AssertionError

I tried testing this out in the shell with some simple conditional statements like this:

>>> str = 'hello'
>>> raise len(str) == 5 or AssertionError

Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    raise len(str) == 5 or AssertionError
TypeError: exceptions must be classes, instances, or strings (deprecated), not bool

So judging from this test, at least the way I tried it, you can't raise a boolean statement. What does it mean to raise a conditional expression then and why does it work in the __init__ function but not in my test code?


Solution

  • The code is nonsense, a botched attempt at something that looks like an assert statement that fails, as you have discovered.

    What they should have written is:

    assert isinstance(matrix, ButtonMatrixElement)
    

    etcetera.

    It appears you found decompiled Ableton Live scripts, but the decompilation script produced wrong Python code. The bytecode for an assert looks like this (Python 2.5 bytecode):

    >>> import dis
    >>> dis.dis(compile('''assert isinstance(matrix, ButtonMatrixElement)''', '<stdin>', 'exec'))
      1           0 LOAD_NAME                0 (isinstance)
                  3 LOAD_NAME                1 (matrix)
                  6 LOAD_NAME                2 (ButtonMatrixElement)
                  9 CALL_FUNCTION            2
                 12 JUMP_IF_TRUE             7 (to 22)
                 15 POP_TOP             
                 16 LOAD_GLOBAL              3 (AssertionError)
                 19 RAISE_VARARGS            1
            >>   22 POP_TOP             
                 23 LOAD_CONST               0 (None)
                 26 RETURN_VALUE        
    

    and it looks as if whatever automated process was used to decompile the bytecode translated that to the code you see rather than recognise that as an assert.

    Note however that if the isinstance() call returns True, the jump instruction (index 12, JUMP_IF_TRUE) jumps past the RAISE_VARARGS instruction, while the re-constructed code doesn't. Compare this to an actual raise ... or ... statement, you'll notice the jump doesn't go past the raise:

    >>> dis.dis(compile('raise foo or bar', '<stdin>', 'exec'))
      1           0 LOAD_NAME                0 (foo)
                  3 JUMP_IF_TRUE             4 (to 10)
                  6 POP_TOP             
                  7 LOAD_NAME                1 (bar)
            >>   10 RAISE_VARARGS            1
                 13 LOAD_CONST               0 (None)
                 16 RETURN_VALUE        
    

    Presumably the code generator was just not sophisticated to handle this; if you assume only or generates JUMP_IF_TRUE and don't handle the offsets properly, you can see how the error was made.