views:

55

answers:

3

I'm trying to take a python class (in IronPython), apply it to some data and display it in a grid. The results of the functions become columns on the grid, and I would like the order of the functions to be the order of the columns. Is there a way to determine the order of python functions of a class in the order they were declared in the source code? I'll take answers that use either IronPython or regular python.

So for instance, if I have a class:

class foo:
    def c(self):
        return 3
    def a(self):
        return 2
    def b(self):
        return 1

I would like to (without parsing the source code myself) get back a list of [c, a, b]. Any ideas?

As a caveat, IronPython used to keep the references of functions in the order they declared them. In .NET 4, they changed this behavior to match python (which always lists them in alphabetical order).

+2  A: 

In Python 2.X (IronPython is currently on Python 2) the answer is unfortunately no. Python builds a dictionary of the class members before creating the class object. Once the class is created there is no 'record' of the order.

In Python 3 metaclasses (which are used to create classes) are improved and you can use a custom object instead of a standard dictionary to collect class members as the class is built. This allows you to know the order.

However, for IronPython there is a hack that might work. Python dictionaries are inherently unordered - i.e. iterating over the members of a dictionary returns them in an arbitrary (but stable) order. In IronPython it used to be the case that, as an accident of implementation, iteration would return members in the order they were inserted. (Note that this may no longer be the case.)

You could try:

 members = list(c.__dict__) # or c.__dict__.keys()

You may find that this is ordered as you hope for. Unfortunately running the code under different versions of IronPython (or other implementations of Python) is likely to return them in a different order.

Another (better but harder) approach is to use the ast module (or the IronPython parser) and walk the AST to find the entries. Not very hard, but more work.

fuzzyman
@fuzzyman: Are you sure you can use metaclasses like that? I'm not good with them but I thought that Python would first parse the class definition into a dict, and then pass that as an argument to the metaclass for construction. But I'd be interested to see that I'm wrong!
katrielalex
@katrielalex: In Python 3 the metaclass can implement a `__prepare__` function which will be called to create the dictionary for the new class namespace - see http://www.python.org/dev/peps/pep-3115/
THC4k
@fuzzyman: Thanks! I ended up sub-classing IronPython's PythonWalker to understand the structure of the class (overriding Walk() and PostWalk()). The way I'm doing it now, I might get more functions than I want (in the case of nested functions) but this doesn't hurt my specific application.
Mike Goodspeed
@fuzzyman: cool! And indeed, the example in that PEP is exactly what the OP wanted.
katrielalex
+2  A: 

Why can't you parse the code yourself? It's easy:

import inspect
import ast

class Foo:
    def c(self):
        pass

    def a(self):
        pass

    def b(self):
        pass

code = ast.parse(inspect.getsource(Foo))

class FunctionVisitor(ast.NodeVisitor):
    def visit_FunctionDef(self, node):
        print(node.name)

FunctionVisitor().visit(code)

outputs

c
a
b
katrielalex
+1 beat me to it
aaronasterling
This was very helpful! I didn't want to parse the code myself, but walking through a syntax tree wasn't half bad.
Mike Goodspeed
+1  A: 

Another solution is to use a decorator to add them into a list.

def make_registry():
    def register(f):
        register.funcs.append(f)
        return f
    register.funcs = []
    return register

register = make_registery()

class Foo(object):

    @register
    def one(self):
        pass

    @register
    def two(self):
        pass

    @register
    def three(self):
        pass

    def utility_method_that_shouldnt_be_run_in_the_sequence(self):
        pass

print register.funcs
aaronasterling