Why doesn't dis module return list of Instructions?

When using the dissassembler, the dis module seems to be the one that disassembles the modules, it returns None. Having a great work, it only prints instructions to the console and it’s not the most useful if it returns None.

I was able to collect the instructions into a list and return them when dis.dis(some_module) is called. Should I open an issue about this?


dis.dis() is for quick debugging or examination. If you want programmatic access, the dis module offers that via dis.get_instructions() which returns an iterator of dis.Instruction objects.

Yes but it doesn’t seem that dis.get_instructions() is able to disassemble python modules.

Yes but it doesn’t seem that dis.get_instructions() is able to
disassemble python modules.

That’s because a module is effectively just a namespace.

You can disassemble code from things which the module references.

Failing example:

 >>> for I in dis.get_instructions(dis): print(I)
 Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
   File "/usr/local/Cellar/python@3.10/3.10.6_1/Frameworks/Python.framework/Versions/3.10/lib/python3.10/dis.py", line 272, in get_instructions
     co = _get_code_object(x)
   File "/usr/local/Cellar/python@3.10/3.10.6_1/Frameworks/Python.framework/Versions/3.10/lib/python3.10/dis.py", line 148, in _get_code_object
     raise TypeError("don't know how to disassemble %s objects" %
 TypeError: don't know how to disassemble module objects

By constrast, asking to disassemble a function from inside the module

 >>> for I in dis.get_instructions(dis.dis): print(I)
 Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='x', argrepr='x', offset=0, starts_line=52, is_jump_target=False)
 Instruction(opname='LOAD_CONST', opcode=100, arg=1, argval=None, argrepr='None', offset=2, starts_line=None, is_jump_target=False)
 Instruction(opname='IS_OP', opcode=117, arg=0, argval=0, argrepr='', offset=4, starts_line=None, is_jump_target=False)
 Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=11, argval=22, argrepr='to 22', offset=6, starts_line=None, is_jump_target=False)
 Instruction(opname='LOAD_GLOBAL', opcode=116, arg=0, argval='distb', argrepr='distb', offset=8, starts_line=53, is_jump_target=False)
 Instruction(opname='LOAD_FAST', opcode=124, arg=1, argval='file', argrepr='file', offset=10, starts_line=None, is_jump_target=False)

and so on, because dis.dis is a function, which has code.

You can use the dir() builtin to find the names inside a module (or
other object):

 >>> dir(dis)
 ['Bytecode', 'COMPILER_FLAG_NAMES', 'EXTENDED_ARG', 'FORMAT_VALUE', 'FORMAT_VALUE_CONVERTERS', 'HAVE_ARGUMENT', 'Instruction', 'MAKE_FUNCTION', 'MAKE_FUNCTION_FLAGS', '_Instruction', '_OPARG_WIDTH', '_OPNAME_WIDTH', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '_disassemble_bytes', '_disassemble_recursive', '_disassemble_str', '_format_code_info', '_get_code_object', '_get_const_info', '_get_instructions_bytes', '_get_name_info', '_have_code', '_test', '_try_compile', '_unpack_opargs', 'cmp_op', 'code_info', 'collections', 'dis', 'disassemble', 'disco', 'distb', 'findlabels', 'findlinestarts', 'get_instructions', 'hascompare', 'hasconst', 'hasfree', 'hasjabs', 'hasjrel', 'haslocal', 'hasname', 'hasnargs', 'io', 'opmap', 'opname', 'pretty_flags', 'show_code', 'stack_effect', 'sys', 'types']

Cameron Simpson cs@cskk.id.au

1 Like

Thanks for your detailed comment. I don’t want to be the person who insists and asks same thing over and over again but what I was saying is, .get_instructions() is great and gives lists of instructions for functions (etc., the ones which contains code). But it does not work for modules.

dis.dis() recursively does the same thing, but for modules. It recursively finds (for example) functions, and dissassembles all the functions inside a module which is very useful. But it only prints to the console. If dis.dis() is for debugging, there’s no alternative function does the same thing and returns a list of Instructions. I played with it a bit, and was able to get a list of Instruction objects of functions inside a module instead of only printing interpreted Instructions (which are only texts, not an Instruction object or class) to the console.

If you look at the relevant source code, you can kinda see why:

    if hasattr(x, '__dict__'):  # Class or module
        items = sorted(x.__dict__.items())
        for name, x1 in items:
            if isinstance(x1, _have_code):
                print("Disassembly of %s:" % name, file=file)
                    dis(x1, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive)
                except TypeError as msg:
                    print("Sorry:", msg, file=file)

It’s very much aimed at interactive use. But if you want the same kind of thing non-interactively, it’s not that hard to iterate over __dict__.items(), maybe map that to a new dictionary that has the gathered instructions.

I don’t really get why dis would target interactive use only, when it could just as easily have targeted both interactive and batch.

But why do you want this? Python’s byte code isn’t guaranteed to remain compatible over time; it’s not like Java, where the bytecode is supported long term.

dis.dis() targets interactive use, where other functions in the module serve other code. For interactive work, it’s fine to make assumptions like “if you give me a thing with a __dict__, I’ll go through it and disassemble everything”, but if you have a function that’s defined as returning a list of instructions, it can be quite annoying to have it suddenly return a dict of lists instead.

I’m doing my Master’s and I plan to create some kind of customized code-in documentation for event-based systems. I found bytecodes & instructions useful for myself. Even though it isn’t guaranteed, I assume that I can update my tool & study related to the change over time.

But only instructing functions were not useful for me. That’s why I opened this topic.