3

I'm trying to figure out how to differentiate between a python module-level function and a method that is not yet bound to an object instance. When the function is an unbound instance method, I need a handle to the class that defined the method. inspect.ismethod() doesn't work because the method is not yet bound to an object instance. type() returns 'function' for both a module-level function and an unbound method.

The use case is a plugin framework that uses method and function decorators. I have a generic 'Model' class that can be subclassed to add functionality. Plugin 'actions' are defined in the subclass using the @register_action( name ) decorator. The decorator function is called on import to register the action. The gui then adds menu items for each ACTION in the dictionary -- but the gui should only add an action if the Model class for the gui is an instance of the class where the function/method was registered.

Some sample code is below. Right now my solution is to parse the text of the fcn descriptor, then check if class_str == str( model.__class__ ).split('.')[-1].strip("'>"). I also tried passing in SpecialModel to the decorator, but SpecialModel is not defined when the decorator function runs so that doesn't work. There must be a better solution for registering class method actions.

Note: since it is not possible to pass SpecialMethod into the register_action decorator because SpecialMethod is not defined, I may need to call get_class_from_function when it comes time to register an action with the gui.

ACTIONS = dict()

### HACK -- best I could do, but want actual Class, not class_name ###
# FIXME : want to return None, Model, SpecialModel, etc. 
def get_class_from_function( fcn ):
    qname = fcn.__qualname__.split( '.' )
    if len( qname ) > 1:     # this is (likely) a class function
        cls_name = qname[0]
        return cls_name
    else:
        return None

def register_action( name ):
    def register_decorator( fcn ):
        global ACTION
        cls = get_class_from_function( fcn )
        ACTION[ name ] = ( fcn, cls )
        return fcn
    return register_decorator

@register_action( "Help/About" )
def help_about_function( model ):
    pass

class Model( object ):
    def __init__( self ):
        pass

    @register_action( "File/Load" )
    def load( self ):
        pass

class SpecialModel( Model ):
    @register_action( "Special/Custom Action" )
    def specialact( self ):
        pass
7
  • 1
    Have you considered checking whether the first argument is self (e.g. args = inspect.getargspec(thing).args; args and args[0] == 'self')? It's no guarantee, but a good indication. Commented Nov 23, 2015 at 20:59
  • 1
    I believe that in Python 3 there is no distinction between a free function and a function defined within a class definition. In fact, you may define a function at the module level and "tack it on" to the class, and it will work just as if it were defined as a method. Perhaps this isn't the best approach... maybe the action of registering a method should be done by the class. That is, whenever a Model is defined, that is when its methods should be registered. This can be done with a class decorator, or, alternatively, a metaclass. Commented Nov 23, 2015 at 21:06
  • class method is a term of art. As far as I can tell, your use of that term is incorrect and just confuses the issue. Commented Nov 23, 2015 at 21:08
  • @jonrsharpe -- yes, that is one of the only differences we could find using the inspect module. It solves the first problem if we are careful about using 'self' instead of slf, s, etc, but it doesn't solve the second part of the problem where we need to check if our model is a subclass of SpecialModel etc. Commented Nov 23, 2015 at 21:09
  • @swinman you cannot get access to the class from an unbound method in 3.x - see e.g. stackoverflow.com/q/3589311/3001761 Commented Nov 23, 2015 at 21:10

2 Answers 2

3

I think you could accomplish your end-goal with a decorator like this:

def register_action( name ):
    def register_decorator( fcn ):
        fcn._action_name = name
        return fcn
    return register_decorator

Then later on in your gui code when you actually have the object instance, iterate over its instance methods and look for any with _action_name and create the gui-specific code for it:

for method_name, method in inspect.getmembers(my_specific_instance, predicate=inspect.ismethod):
   if hasattr(method, '_action_name'):
       add_to_gui_menu(method._action_name, method)
Sign up to request clarification or add additional context in comments.

1 Comment

thanks, this works (after minor syntax fixes). inspect.getmembers() returns (mname, method) tuple. Also, change __action_name__ to _action_name because trailing __ are for python special and leading __ mangles the variable name
2

This should help to distinguish a function defined in a module from a function defined in a class:

import sys
import types

def is_module_func(obj):
    mod = sys.modules[obj.__module__]
    return isinstance(obj, types.FunctionType) and hasattr(mod, obj.__name__)

Now:

class A:
    def meth(self):
        pass

def func():
    pass

>>> is_module_func(func)
True
>>> is_module_func(A.meth)
False

2 Comments

thanks - unfortunately this doesn't work from within the @register decorator because hasattr( mod, obj.__name__) returns false, and it gets confused when the module and function have the same name
to clarify: hasattr returns false because the object has not yet been added to the module when the decorator runs

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.