views:

344

answers:

3

Given an instance of some class in Python, it would be useful to be able to determine which line of source code defined each method and property (e.g. to implement [1]). For example, given a module ab.py

class A(object):
    z = 1
    q = 2
    def y(self): pass
    def x(self): pass

class B(A):
    q = 4
    def x(self): pass
    def w(self): pass

define a function whither(class_, attribute) returning a tuple containing the filename, class, and line in the source code that defined or subclassed attribute. This means the definition in the class body, not the latest assignment due to overeager dynamism. It's fine if it returns 'unknown' for some attributes.

>>> a = A()
>>> b = B()
>>> b.spigot = 'brass'
>>> whither(a, 'z')
("ab.py", <class 'a.A'>, [line] 2)
>>> whither(b,  'q')
("ab.py", <class 'a.B'>, 8)
>>> whither(b, 'x')
("ab.py", <class 'a.B'>, 9)
>>> whither(b, 'spigot')
("Attribute 'spigot' is a data attribute")

I want to use this while introspecting Plone, where every object has hundreds of methods and it would be really useful to sort through them organized by class and not just alphabetically.

Of course, in Python you can't always reasonably know, but it would be nice to get good answers in the common case of mostly-static code.

+1  A: 

You are looking for the inspect module, specifically inspect.getsourcefile() and inspect.getsourcelines(). For example

a.py:

class Hello(object):
    def say(self):
       print 1

>>> from a import Hello
>>> hi = Hello()
>>> inspect.getsourcefile(hi.say)
a.py
>>> inspect.getsourcelines(A, foo)
(['   def say(self):\n        print 1\n'], 2)

Given the dynamic nature of Python, doing this for more complicated situations may simply not be possible...

dF
inspect.getmembers(portal)>>> AttributeError: index_htmlthough, inspect.getmembers(portal.__class__) works...
joeforker
+2  A: 

This is more-or-less impossible without static analysis, and even then, it won't always work. You can get the line where a function was defined and in which file by examining its code object, but beyond that, there's not much you can do. The inspect module can help with this. So:

import ab
a = ab.A()
meth = a.x
# So, now we have the method.
func = meth.im_func
# And the function from the method.
code = func.func_code
# And the code from the function!
print code.co_firstlineno, code.co_filename

# Or:
import inspect
print inspect.getsource(meth), inspect.getfile(meth)

But consider:

def some_method(self):
    pass
ab.A.some_method = some_method
ab.A.some_class_attribute = None

Or worse:

some_cls = ab.A
some_string_var = 'another_instance_attribute'
setattr(some_cls, some_string_var, None)

Especially in the latter case, what do you want or expect to get?

Aaron Gallagher
+1: not a sensible request; also, essentially. impossible.
S.Lott
-1. Of course I know it's really hard to do a perfect job, but inspect.classify_class_attrs(cls) is pretty close. Take that S.Lott., naysayer.
joeforker
@Daniel: There's no way to track where things were assigned. The only way you can get the information you want is by using the things in the inspect module with methods or functions. Arbitrary attributes can only be tracked with static analysis.
Aaron Gallagher
@Aaron: When I say 'defined' I mean the definition def method(): pass, not 'assigned' as in ab.A.some_method = method. Even for your some_string_var example, you can know it's part of A and not a super or subclass.
joeforker
+1  A: 

You are looking for the undocumented function inspect.classify_class_attrs(cls). Pass it a class and it will return a list of tuples ('name', 'kind' e.g. 'method' or 'data', defining class, property). If you need information on absolutely everything in a specific instance you'll have to do additional work.

Example:

>>> import inspect
>>> import pprint
>>> import calendar
>>> 
>>> hc = calendar.HTMLCalendar()
>>> hc.__class__.pathos = None
>>> calendar.Calendar.phobos = None
>>> pprint.pprint(inspect.classify_class_attrs(hc.__class__))
[...
 ('__doc__',
  'data',
  <class 'calendar.HTMLCalendar'>,
  '\n    This calendar returns complete HTML pages.\n    '),
 ...
 ('__new__',
  'data',
  <type 'object'>,
  <built-in method __new__ of type object at 0x814fac0>),
 ...
 ('cssclasses',
  'data',
  <class 'calendar.HTMLCalendar'>,
  ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']),
 ('firstweekday',
  'property',
  <class 'calendar.Calendar'>,
  <property object at 0x98b8c34>),
 ('formatday',
  'method',
  <class 'calendar.HTMLCalendar'>,
  <function formatday at 0x98b7bc4>),
 ...
 ('pathos', 'data', <class 'calendar.HTMLCalendar'>, None),
 ('phobos', 'data', <class 'calendar.Calendar'>, None),
 ...
 ]
joeforker
did you mean to answer your own question?
Triptych
I was trying to avoid answering my own question, but since none of the answers I got to my 2 related SO questions were exactly what I was looking for I had no choice but to dig through and find it myself.
joeforker