3

My question is somewhat similar to this one; it concerns object methods rather than module contents. I want to know if I can use the inspect module to get the methods defined only in the class I'm asking about, and not its parent(s).

I need this because my child class defines 'macro' methods, which accesses the parent's methods at a higher level of abstraction, and I don't want the user to have to worry about the lower-level methods defined all the way up the inheritance tree.

Here is a simplified example:

class Foo(object):
    def __init__(self): pass
    def f1(self): return 3
    def f2(self): return 1

class Bar(Foo):
    def __init__(self): Foo.__init__(self)
    def g1(self): return self.f1() + self.f2()
    def g2(self): return self.f1() - self.f2()

import inspect
inspect.getmembers(Bar, inspect.ismethod)

Output:

[('__init__', <unbound method Bar.__init__>),
 ('f1', <unbound method Bar.f1>),
 ('f2', <unbound method Bar.f2>),
 ('g1', <unbound method Bar.g1>),
 ('g2', <unbound method Bar.g2>)]

The user need not know or care about the existence of the fs since she's only ever going to be interested in the gs. (Of course, this output makes sense in the vast majority of contexts, since all these methods will be bound to the object when it is instantiated.) For a long inheritance tree, the returned list can get very long and full of things that aren't relevant to the user.

How can I get it to leave f1 and f2 off this list? Is there an equivalent to the __module__ attribute for the methods defined in classes? Better still, is it possible to do the same thing with instance methods?

Community
  • 1
  • 1
Benjamin Hodgson
  • 42,952
  • 15
  • 108
  • 157
  • Asking for `im_class` on a bound method (of an instance of `Bar`) doesn't work. Nor does it work on the unbound methods. – Benjamin Hodgson Aug 13 '12 at 12:23
  • 1
    `object.hasOwnPro…` no wait, that's not right. (Sorry, I just couldn't believe there was something JavaScript does more easily than Python.) – kojiro Aug 13 '12 at 12:25
  • The term `im_class` seems to be a Python 2 attribute no longer present in Python 3. You'll pick it up eventually if you read forward enough but to get you started quick check out this answer https://stackoverflow.com/a/59548793. Note **also** Python 3 methods are *not* just functions with an additional first parameter, they *also* have attributes as a result of binding to a class or instance such as `__self__`. – NeilG Mar 01 '23 at 23:38

2 Answers2

5

Methods have an im_class attribute, that points to the class in question. You can use that filter on functions that are members of the class:

inspect.getmembers(Bar,
    lambda m: inspect.ismethod(m) and m.__func__ in m.im_class.__dict__.values())

This gives you:

[
    ('__init__', <unbound method Bar.__init__>),
    ('f1', <unbound method Bar.f1>), 
    ('f2', <unbound method Bar.f2>)
]

Of course, you can just bypass getmembers altogether then:

[m for m in Bar.__dict__.values() if inspect.isfunction(m)]

gives:

[<function __init__ at 0x100a28de8>, <function g1 at 0x100a28e60>, <function g2 at 0x100a28ed8>]

This applies to bound or unbound methods, they have same .__func__ (or im_func, the old name) attribute. The difference between bound and unbound is the value of the .__self__ attribute (None when unbound).

These "secret" attributes are all documented in the Python Data Model.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    Wow ... This seems a little like black magic. Where do you learn this stuff? (+1) – mgilson Aug 13 '12 at 12:34
  • @mgilson: added a pointer; also, `dir()` is your friend, as always. Though, it must be said, having worked with some of the greatest Python developers ever at various points in my career has had it's advantages. :-) – Martijn Pieters Aug 13 '12 at 12:35
  • Ahh ... I've never had much need to dig that deep into the inner workings of python and it's amazing introspection abilities (as is probably obvious by my naive answer). I'm just frequently surprised by your ability to answer these interesting questions as though you have the entire data model memorized. – mgilson Aug 13 '12 at 12:47
  • @MartijnPieters I'm not very familiar with things like `__dict__` and how they actually work. Would you be able to explain the difference between `__dict__`, `dir()` and `inspect.getmembers()`, when called on either class objects or instance objects? They all seem to give different outputs – Benjamin Hodgson Aug 13 '12 at 13:57
  • 1
    @poorsod: `__dict__` is the basic namespace of a class; both `dir()` and `inspect.getmembers()` use this together with other information to build larger structures (including recursing down the `.__bases__` class hierarchy. – Martijn Pieters Aug 13 '12 at 13:59
  • @MartijnPieters: I have two quick questions about your idea `[m for m in Bar.__dict__.values() if inspect.isfunction(m)]`. 1) Why do you need to use `isfunction()` and not `ismethod()`? 2) Why, when using this on an _instance_, do I need to use `a.__class__.__dict__` and not just `a.__dict__`? I thought objects carried their methods around with them - or was that just an illusion? – Benjamin Hodgson Aug 13 '12 at 16:53
  • @poorsod: Methods are bound functions; the `__dict__` holds functions before binding. The instance `__dict__` contains instance values, but inherits methods from it's class (you can add methods to an instance independent from it's class). – Martijn Pieters Aug 13 '12 at 16:55
  • @MartijnPieters: So why does the class `__dict__` hold functions and not unbound methods? For that matter, what is the distinction between the two? (Perhaps this should be a separate question?) – Benjamin Hodgson Aug 13 '12 at 17:02
  • @poorsod: A bound method is a function with a class pointer, and optionally a instance pointer (`im_class` and `__self__`). Functions are [descriptors](http://docs.python.org/reference/datamodel.html#invoking-descriptors), read up on the whole process there. :-) – Martijn Pieters Aug 13 '12 at 17:06
  • Thanks @MartijnPieters, I think I have to conclude that I don't have the time to grok Python descriptors for this job, and as SO is now pointing out, I think this discussion has outgrown this medium right now so I won't comment further. I appreciate the exact clarification there. Thankyou. P.S.: this: https://link.springer.com/book/10.1007/978-1-4842-3727-4 – NeilG Mar 03 '23 at 02:24
2

Hopefully someone will come along with a better solution, but what about:

foo_members = inspect.getmembers(Foo,inspect.ismethod)
bar_members = inspect.getmembers(Bar,inspect.ismethod)

set(bar_members) - set(foo_members)

Of course, in a real situation, you might need to walk through Bar.__bases__ to actually get rid of everything you don't want.

e.g.:

set(bar_members) - set(sum([inspect.getmembers(base,inspect.ismethod) for base in Bar.__bases__],[]))
mgilson
  • 300,191
  • 65
  • 633
  • 696