Python: Get fully qualified name of static function object

Hi,

I’m trying to get the fully qualified name of a static function objects in Python, say: “proj.package.module.classname.functionname”.

I can use “function.module” and “function.name” to get the module and functions names, but I’ve not been able to find the class name from the static function object.

Any help would be great :slight_smile:

instance.class.name ?

Staticmethods don’t know anything about what class they are attached to, they are basically module level functions that happen to be defined on a class object instead of a module object.

Something like this can work if the class isn’t defined inside a function or as a nested class, you do still need a reference to the static method itself though.


import sys
import inspect

class StaticTest(object):
    
    @staticmethod
    def static_method():
        pass


st_sm = StaticTest.static_method
mod = sys.modules[st_sm.__module__]
for item in dir(mod):
    item = getattr(mod, item)
    if inspect.isclass(item):
        for cls_item in dir(item):
            cls_item = getattr(item, cls_item)
            if st_sm == cls_item:
                print('.'.join([st_sm.__module__, item.__name__ ,st_sm.__name__]))

# Should print the following:
# __main__.StaticTest.static_method


inspect is the way to go: It’s more consistent than most of the alternatives. I think you could modify rob’s example to use inspect.getmodule as well to keep everything less magical

Will inspect actually do anything for staticmethods though? I couldn’t find anything in there that would return that.

Ah, you don’t have a handle to the instance. How about stack trace?

You could try inspect.getouterframes and look for the object which contains the the method… icky though…
You could just punt and put it in the docstring. That’s almost always available as <function.doc> or inspect.getdoc(function)

Another option, if you have control over the definition, is just use a classmethod instead. That way you do get the reference to the class object, even if you don’t use it as part of the method.

Some context: I’m creating a Python wrapper around some of 3dsMax’s MaxScript Callback systems. To set up a callback, the user provides a function to be called:

add_callback(event, function_object)

My wrapper should accent any valid function type (module level, instance, class or static) and be able figure out it’s fully qualified path, which I need to build my Callback in MaxScript land.

If there is no clean and reliable way to get the fully qualified name, I might use one of these fall-backs:
[ul]
[li]Instead of a function object, the user provides the fully qualified function path as a sting (kinda clunky and need maintenance, if function path changes)[/li][li]I store a reference to the user provided method in Python. MaxScript instead calls a method defined in my wrapper (which I know the path to), which forwards the call the the user provided method (An additional layer of complexity in my code and method references may go out of scope or become invalid if stuff is reloaded)[/li][/ul]

Thanks for the help so far :slight_smile:

So MaxScript is going to import ant call the function again? That’s why you need the FQN?

Your second option seems like the easy way to go. You have a manager class which map the callback ID’s max use to python and handles the instantiation, then Max only needs to know one thing about python land:


class CallbackManager(object):
    
     @classmethod
     def nullop(*args, **kwargs):
           print "Max event not handled: ", args

      @classmethod
      def register(cls, id, fn):
           cls.CALLBACKS[id] = fn

      @classmethod
      def call(id, event):
          handler = cls.get(id, cls.CALLBACKS['nullop'])
          handler(event)

     CALLBACKS = {
          'nullop' : nullop
     }


and then in max


path.to.module.CallbackManager.call('id', event)

… if I’m understanding you correctly. Lots of people do something like this to handle a mix of mel and python interchangeably in Mayta

[QUOTE=Theodox;28843]… if I’m understanding you correctly. Lots of people do something like this to handle a mix of mel and python interchangeably in Mayta[/QUOTE]

I use something similar for working with the OpenMaya.MMessage callbacks.
It involves keeping weakref’s to the various methods / functions, but works fairly well.

@theodox Actually that was not what I was planning exactly, but that definitely seems like the way to go about it. Will also keep down the amount of callbacks floating around in MaxScript land. I’m glad we had this talk :slight_smile:

If you go that route you can easily make the registration a decorator too. Something like:


class MaxCallback(object):
       def __init__(self, name):
            self.name = name

      def __call__(self, fn):
           CallbackManager.register(self.name, fn)
           return fn

@MaxCallback("example")
def some_code(*args):
     pass


Warning, that’s completely un-tested code and I usually screw up decorators the first 10 times

I’ve just begun looking at this again. I store lists of function objects, that are to get called when a specific event fires. In you experience, is it necessary to refer these object with weakref?

If these are static or class methods the weakrefs will never expire anyway (unless maybe you del() the owning module). If they are instance methods they should be weakrefs, otherwise the event handler will keep the objects alive when nobody else wants them

Gotcha, weakref it is. Thanks :slight_smile:

This might be relevant

Great, thanks for all the input, things are now up and running.

In order to hold valid weekrefs to instance methods, I ended up snagging the following “week method” code. It’s behavior and interface is similar to that of the weekref module:

import weakref
import new


class ref(object):

    def __init__(self, method):
        try:
            if method.im_self is not None:
                # bound method
                self._obj = weakref.ref(method.im_self)
            else:
                # unbound method
                self._obj = None
            self._func = method.im_func
            self._class = method.im_class
        except AttributeError:
            # not a method
            self._obj = None
            self._func = method
            self._class = None


    def __call__(self):
        '''Return a new bound-method like the original, or the
        original function if refers just to a function or unbound
        method.
        Returns None if the original object doesn't exist
        '''
        if self.is_dead():
            return None
        if self._obj is not None:
            # we have an instance: return a bound method
            return new.instancemethod(self._func, self._obj(), self._class)
        else:
            # we don't have an instance: return just the function
            return self._func


    def is_dead(self):
        '''Returns True if the referenced callable was a bound method and
        the instance no longer exists. Otherwise, return False.
        '''
        return self._obj is not None and self._obj() is None


    def __eq__(self, other):
        try:
            return type(self) is type(other) and self() == other()
        except:
            return False


    def __ne__(self, other):
        return not self == other


Note that calling the ref has the same semanthics as calling a weakref.ref: if the referent died, it returns None. If you want something like weakref.proxy:

class proxy(ref):
    '''Exactly like ref, but calling it will cause the referent method to
    be called with the same arguments. If the referent's object no longer lives,
    ReferenceError is raised.
    '''

    def __call__(self, *args, **kwargs):
        func = ref.__call__(self)
        if func is None:
            raise ReferenceError('object is dead')
        else:
            return func(*args, **kwargs)


    def __eq__(self, other):
        try:
            func1 = ref.__call__(self)
            func2 = ref.__call__(other)
            return type(self) == type(other) and func1 == func2
        except:
            return False

Code can be found in the comments here: WeakMethod « Python recipes « ActiveState Code