4

I am trying to implement infer_class function that, given a method, figures out the class to which the method belongs.

So far I have something like this:

import inspect

def infer_class(f):
    if inspect.ismethod(f):
        return f.im_self if f.im_class == type else f.im_class
    # elif ... what about staticmethod-s?
    else:
        raise TypeError("Can't infer the class of %r" % f)

It does not work for @staticmethod-s because I was not able to come up with a way to achieve this.

Any suggestions?

Here's infer_class in action:

>>> class Wolf(object):
...     @classmethod
...     def huff(cls, a, b, c):
...         pass
...     def snarl(self):
...         pass
...     @staticmethod
...     def puff(k,l, m):
...         pass
... 
>>> print infer_class(Wolf.huff)
<class '__main__.Wolf'>
>>> print infer_class(Wolf().huff)
<class '__main__.Wolf'>
>>> print infer_class(Wolf.snarl)
<class '__main__.Wolf'>
>>> print infer_class(Wolf().snarl)
<class '__main__.Wolf'>
>>> print infer_class(Wolf.puff)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in infer_class
TypeError: Can't infer the class of <function puff at ...>
ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
Pavel Repin
  • 30,663
  • 1
  • 34
  • 41
  • 1
    You have the source, you can read the parent class. Why do you need this? What are you trying to accomplish? – S.Lott Jun 04 '09 at 09:52
  • 2
    Suppose I wanted to write a function that temporarily stubs a function or a method (to intercept calls or whatever, for testing purposes). To be able to do that, I need two ingredients: object that contains the function, and the function name, so that I can do `setattr(obj, func_name, my_stub)`. If f is a module-level function, I use `inspect.getmodule(f)` to obtain the object and `f.__name__` to get its name. For class methods & instance methods, I use the code above. For static methods, I am out of luck, it would seem. – Pavel Repin Jun 04 '09 at 17:45

2 Answers2

3

That's because staticmethods really aren't methods. The staticmethod descriptor returns the original function as is. There is no way to get the class via which the function was accessed. But there is no real reason to use staticmethods for methods anyway, always use classmethods.

The only use that I have found for staticmethods is to store function objects as class attributes and not have them turn into methods.

Ants Aasma
  • 53,288
  • 15
  • 90
  • 97
  • 6
    -1: "But there is no real reason to use staticmethods for methods anyway, always use classmethods." Do you mean instancemethods or classmethods? There are valid use cases for staticmethods, sometimes there are "real reasons" to use them. – nikow Jun 04 '09 at 11:58
  • 2
    I'm interested where would a staticmethod be preferrable to a classmethod? As I said, the only compelling usecase is storing functions as class attributes. (and I don't consider "but I don't like the cls parameter" a compelling argument) – Ants Aasma Jun 04 '09 at 12:27
  • 2
    +1 Agreed. I've also never seen a use for staticmethod in Python besides for storing a function object as a class attr. – Carl Meyer Jun 04 '09 at 22:30
3

I have trouble bringing myself to actually recommend this, but it does seem to work for straightforward cases, at least:

import inspect

def crack_staticmethod(sm):
    """
    Returns (class, attribute name) for `sm` if `sm` is a
    @staticmethod.
    """
    mod = inspect.getmodule(sm)
    for classname in dir(mod):
        cls = getattr(mod, classname, None)
        if cls is not None:
            try:
                ca = inspect.classify_class_attrs(cls)
                for attribute in ca:
                    o = attribute.object
                    if isinstance(o, staticmethod) and getattr(cls, sm.__name__) == sm:
                        return (cls, sm.__name__)
            except AttributeError:
                pass
Jacob Gabrielson
  • 34,800
  • 15
  • 46
  • 64
  • Or maybe just 1 line: next((k for k,v in sys.modules[sm.__module__].__dict__.items() if getattr(v,sm.__name__,None) is sm),None) – parity3 Apr 25 '17 at 01:09