1

I have a library that can take either a class function or a normal function with certain signatures. A class normally looks like this:

class MyObject(object):
    def __init__(self, request):
       self.request = request

    def run(self):
        return 'OK'

and the function format is this:

def my_func(request):
    retrn 'OK'

The usage of this is:

add_runner(MyObject, attr='run')
add_runner(my_func)

but sometimes people utilize this wrong and pass the class function to my library:

add_runner(MyObject.run)

I have detected this in Python 2.x as an unbound method and raise an error to alert them that they are using my API wrong but in Python 3.x I can't find any way to actually detect they are doing this wrong. What am I missing?

sontek
  • 12,111
  • 12
  • 49
  • 61

3 Answers3

0

I think this works well, I just hate that I have to detect self

def _is_unbound(fn):
    """
    This consistently verifies that the callable is bound to a
    class.
    """
    spec = inspect.getargspec(fn)
    has_self = len(spec.args) > 0 and spec.args[0] == 'self'
    is_bound = getattr(fn, im_self, None) is not None

    if not is_bound:
        if PY3 and inspect.isfunction(fn) and has_self:
            return True
        elif inspect.ismethod(fn):
            return True

    return False
sontek
  • 12,111
  • 12
  • 49
  • 61
0

With python 3, unbounded methods are exactly equivalent to functions ( see discussion in Get defining class of unbound method object in Python 3 )

Checking the first argument name == 'self' can be problematic, since there is no requirement for the first argument of a method to be 'self'. Any valid argument name could be used (e.g., 'myself', 'this', etc).

I would suggest that use continue to use the argument inspection, to ensure that the caller has provided a function with call signature that you expect.

In python3 it is possible for the unbound method to properly be passed to add_runner:

class AnotherObject:
   def run(request):
     return "OK"

add_runner(AnotherObject.run)
Community
  • 1
  • 1
levis501
  • 4,117
  • 23
  • 25
  • Yes, but what I want to detect is invalid usage. So if `run` is an instance method (i.e tied to an instance of the class via `self`) then it is invalid because I don't want a self to pass it – sontek Dec 28 '14 at 00:01
  • In the case that `run` is an instance method, `inspect.ismethod(objectInstance.run)` would return `True` – levis501 Dec 28 '14 at 02:10
0

If you can assume Py27 and later, it seems like the following would work:

def is_bound(method):
    return bool(getattr(method, '__self__', None))

In Py26, the __self__ attribute was added in addition to im_self for fowards compatibility with Py3 (see the documentation for details).

In Py2, an unbound method will have a __self__ but the value will be None. A bound method will have a __self__ that corresponds to the instance of the class where it was called.

In Py3, a "bound" method (one called on an instance of a class) will define __self__. In contrast, an "unbound" method (one called on the class definition itself) will not define __self__.

ddavella
  • 595
  • 6
  • 12