1

I have two classes:

class A(object):
  def a(self):
    pass

class B(A):
  def b(self):
    pass

print dir(A)
print dir(B)

How can I check from which class methods is derived in Python?

For example:

getMethodClass(A.a) == A
getMethodClass(B.a) == A
getMethodClass(B.b) == B
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Chameleon
  • 9,722
  • 16
  • 65
  • 127
  • FYI `getattr(A, 'a')` is just a silly way to write `A.a`, and `type(A)` is a Python expression that evaluates to `type` - you probably want just `A`. –  Apr 06 '14 at 18:26
  • @delnan Read question again I was improve it as you suggest - I make mistake indeed good hint. – Chameleon Apr 06 '14 at 18:31
  • See also http://stackoverflow.com/questions/22898037/how-to-check-in-python-from-which-class-methods-is-derived/ which asks how to do the same thing from _inside_ the method, rather than from _outside_. – abarnert Sep 09 '14 at 18:20

1 Answers1

1

Interesting question. Here is how I'd go about it.

(This works in python2. I haven't tested it in python3, but I won't be surprised if it does not work...)

You can iterate over all the "nominees" using reversed(inspect.getmro(cls)), and returning the first (by fetching the next value of the iterator) which satisfy the condition that it has the relevant attr, and that attr is the same as the method of the relevant cls.

Method identity-comparison is done by comparing the im_func attribute of the unbound method.

import inspect

def getMethodClass(cls, attr):
   return next(
      basecls for basecls in reversed(inspect.getmro(cls))
      if hasattr(basecls, attr)
      and getattr(basecls, attr).im_func is getattr(cls, attr).im_func
   )

getMethodClass(A, 'a')
=> __main__.A
getMethodClass(B, 'a')
=> __main__.A
getMethodClass(B, 'b')
=> __main__.B

# an alternative implementation, suggested by @chameleon
def getAttributeClass(cls, attrName):
  # check first if has attribute
  attr = getattr(cls, attrName)

  mro = inspect.getmro(cls)
  # only one class on list
  if len(mro) == 1:
    return cls

  # many class on list
  for base in reversed(mro[1:]):
    # check if defined in this base
    try:
      baseAttr = getattr(base, attrName)
    except AttributeError:
      continue
    else:
      if baseAttr.im_func is attr.im_func:
        return base
  # define in top class
  return cls

The function can also have the signature you suggest:

def getMethodClass(unbound_method):
    cls = unbound_method.im_class
    attr = unbound_method.__name__
    # rest of implementation is the same as before...

getMethodClass(B.a)
=> __main__.A
Chameleon
  • 9,722
  • 16
  • 65
  • 127
shx2
  • 61,779
  • 13
  • 130
  • 153
  • Looks good but I think better is to use `inspect.getmro(cls)` and not use `reverse` whatever it should work. What do you think? You can also skip first on mro() since first is class tested. – Chameleon Apr 06 '14 at 19:28
  • I guess you're right about using `inspect.getmro`. I'm not sure how you'd expect it to work without `reverse`ing -- it would always return `cls`. Also, skipping the first on `mro()` would make the code more complex, with no apparent benefit -- you'd still need to return `cls` in some cases, so why not include it in the list you iterate over? – shx2 Apr 06 '14 at 19:32
  • Why exclude first since first always pass condition `getattr(cls, attr).im_func == getattr(cls, attr).im_func` you can set result to cls and try to replace it with first hit `result = cls for x in mro(): if x != cls and ...: result = x` - do you understand me? – Chameleon Apr 06 '14 at 19:37
  • It will lead to such construct x = something, search in loop for replace for x if no replace x will be not changed. BTW for me next() is not know since very rare I will train it. – Chameleon Apr 06 '14 at 19:39
  • Yes, your alternative implementation would work just as well. – shx2 Apr 06 '14 at 19:40