4

I need to perform some black and dirty magic with Python.

Following my previous question Lazy data-flow (spreadsheet like) properties with dependencies in Python now I'd like to populate the list of fields a derived property depends on automagically by inspecting the calculate function.

I think I could use func_code.co_names attribute, but I'm not very sure if there are some caveats there and the documentation does not help much.

Any ideas?

Community
  • 1
  • 1
fortran
  • 74,053
  • 25
  • 135
  • 175

1 Answers1

2

Unfortunately func_code.co_names is not likely going to help much. This contains all names that are accessed within the code segment, including global variables, in order of appearance.

class Test(object):
    def calc_a(self):
        return self.b + self.c

    def calc_x(self):
        return self.y.a + self.y.b

>>> Test.calc_a.func_code.co_names
('b', 'c')
>>> Test.calc_x.func_code.co_names
('y', 'a', 'b')

It is not possible to tell from this array if 'a' and 'b' loaded from 'self' or from 'self.y'. Generally, the only way to know the access pattern of a bit of code without executing it is to disassemble it.

>>> import dis
>>> dis.dis(Test.calc_x)
 23           0 LOAD_FAST                0 (self)
              3 LOAD_ATTR                0 (y)
              6 LOAD_ATTR                1 (a)
              9 LOAD_FAST                0 (self)
             12 LOAD_ATTR                0 (y)
             15 LOAD_ATTR                2 (b)
             18 BINARY_ADD          
             19 RETURN_VALUE        

We see that the function loads the 'self' variable (which is always co_varnames[0] for a bound function), then from that object loads the attribute 'y' (co_names[0]), and then from that object loads the attribute 'a' (co_names[1]). A second stack object is pushed from self.y.b, then the two are added.

Look at the source of dis.py in the standard lib to see how the C Python binary code is disassembled. Loads of the 0th variable will be important for bound functions. Another handy point is that the arguments to the function are co_varnames[:co_argcount] (the rest or varnames are locals) and co_freevars are variables from an enclosing non-global scope.

Jeremy Fishman
  • 1,044
  • 11
  • 11