views:

96

answers:

3

The dis module can be effectively used to disassemble Python methods, functions and classes into low-level interpreter instructions.

I know that dis information can be used for:
1. Find race condition in programs that use threads
2. Find possible optimizations

From your experience, do you know any other scenarios where Disassembly Python feature could be useful?

+4  A: 

I see the dis module as being, essentially, a learning tool. Understanding what opcodes a certain snippet of Python code generates is a start to getting more "depth" to your grasp of Python -- rooting the "abstract" understanding of its semantics into a sample of (a bit more) concrete implementation. Sometimes the exact reason a certain Python snippet behaves the way it does may be hard to grasp "top-down" with pure reasoning from the "rules" of Python semantics: in such cases, reinforcing the study with some "bottom-up" verification (based on a possible implementation, of course -- other implementations would also be possible;-) can really help the study's effectiveness.

Alex Martelli
Good point, thank you.
systempuntoout
+2  A: 

For day-to-day Python programming, not much. However, it is useful if you want to find out why doing something one way is faster than another way. I've also sometimes used it to figure out exactly how the interpreter handles some obscure bits of code. But really, I come up with a practical use-case for it very infrequently.

On the other hand, if your goal is to understand python rather than just being able to program in it, then it is an invaluable tool. For instance, ever wonder how function definition works? Here you go:

>>> def f():
...     def foo(x=[1, 2, 3]):
...         y = [4,]
...         return x + y
... 
>>> dis(f)
  2           0 LOAD_CONST               1 (1)
              3 LOAD_CONST               2 (2)
              6 LOAD_CONST               3 (3)
              9 BUILD_LIST               3
             12 LOAD_CONST               4 (<code object foo at 0xb7690770, file "<stdin>", line 2>)
             15 MAKE_FUNCTION            1
             18 STORE_FAST               0 (foo)
             21 LOAD_CONST               0 (None)
             24 RETURN_VALUE        

You can see that this happens by pushing the constants 1, 2, and 3 onto the stack, putting what's in the stack into a list, loading that into a code object, making the code function into an object, and storing it into a variable foo.

Jason Baker
by choosing 1,2,3 as constants (instead of some 'weirder' group like 5,13,6) it diminishes the example a bit, imho.
Gregg Lind
+6  A: 

dis is useful, for example, when you have different code doing the same thing and you wonder where the performance difference lies in.

Example: list += [item] vs list.append(item)

def f(x): return 2*x

def f1(func, nums):
  result = []
  for item in nums:
    result += [fun(item)]
  return result

def f2(func, nums):                       
  result = []
  for item in nums:
    result.append(fun(item))
  return result

timeit.timeit says that f2(f, range(100)) is approximately twice as fast than f1(f, range(100). Why?

(Interestingly f2 is roughly as fast as map(f, range(100)) is.)

f1

You can see the whole output of dis by calling dis.dis(f1), here is line 4.

  4          19 LOAD_FAST                2 (result)
             22 LOAD_FAST                1 (fun)
             25 LOAD_FAST                3 (item)
             28 CALL_FUNCTION            1 
             31 BUILD_LIST               1 
             34 INPLACE_ADD                
             35 STORE_FAST               2 (result) 
             38 JUMP_ABSOLUTE           13 
        >>   41 POP_BLOCK           

f2

Again, here is only line 4:

  4          19 LOAD_FAST                2 (result)
             22 LOAD_ATTR                0 (append)
             25 LOAD_FAST                1 (fun)
             28 LOAD_FAST                3 (item)
             31 CALL_FUNCTION            1 
             34 CALL_FUNCTION            1 
             37 POP_TOP                    
             38 JUMP_ABSOLUTE           13 
        >>   41 POP_BLOCK           

Spot the difference

In f1 we need to:

  • Call fun on item (opcode 28)
  • Make a list out of it (opcode 31, expensive!)
  • Add it to result (opcode 34)
  • Store the returned value in result (opcode 35)

In f2, instead, we just:

  • Call fun on item (opcode 31)
  • Call append on result (opcode 34; C code: fast!)

This explains why the (imho) more expressive list += [value] is much slower than the list.append() method.


Other than that, dis.dis is mainly useful for curiosity and for trying to reconstruct code out of .pyc files you don't have the source of without spending a fortune :)

badp
Bonus points for figuring out what opcode 37 in `f2` does.
badp
+1 Great example.grazie
systempuntoout