Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test if an object is a function vs. an unbound method?

def is_unbound_method(func):
    pass

def foo(): pass

class MyClass(object):
    def bar(self): pass

What can I put in the body of is_unbound_method so that

is_unbound_method(foo) == False
is_unbound_method(MyClass().bar) == False
is_unbound_method(MyClass.bar) == True

??

like image 776
Dan Passaro Avatar asked Dec 19 '22 12:12

Dan Passaro


2 Answers

An unbound method has __self__ set to None:

def is_unbound_method(func):
    return getattr(func, '__self__', 'sentinel') is None

Demo:

>>> foo.__self__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'function' object has no attribute '__self__'
>>> is_unbound_method(foo)
False
>>> MyClass.bar.__self__
>>> is_unbound_method(MyClass.bar)
True
>>> MyClass().bar.__self__
<__main__.MyClass object at 0x106c64a50>
>>> is_unbound_method(MyClass().bar)
False

The attribute is also available as .im_self, but __self__ is forward compatible.

Note that in Python 3 unbound methods are gone; accessing MyClass.bar returns the function object. Thus the above function will always return False.

See the Datamodel documentation, in the User-defined methods section:

Special read-only attributes: im_self is the class instance object, im_func is the function object

[...]

Changed in version 2.6: For Python 3 forward-compatibility, im_func is also available as __func__, and im_self as __self__.

[...]

When a user-defined method object is created by retrieving a user-defined function object from a class, its im_self attribute is None and the method object is said to be unbound.

like image 119
Martijn Pieters Avatar answered Feb 15 '23 23:02

Martijn Pieters


In Python 3 there's no reliable way to determine that just from the function object (since any function defined in a class is just a function like any other).

Perhaps a sub-optimal approach is to inspect the signature and check for self as the first parameter:

import inspect


def is_unbound_method(obj):
    signature = inspect.signature(obj)
    if not signature.parameters:
        return False
    return next(iter(signature.parameters), None) == "self"

But of course, this depends on the first parameter being named self (and on other functions not using self as the first parameter), which is just a convention.

If you already know the object is defined within a class, perhaps a better approach is to check whether it's a callable that's not a classmethod or a staticmethod:

import inspect


def is_unbound_method_from(cls, obj):
    return bool(
        callable(obj) 
        and not isinstance(obj, (classmethod, staticmethod))
        and inspect.getmembers(cls, lambda m: m is obj)
    )

You should reorder the clauses in the conjunction above from what you believe is least likely to most likely (to avoid unnecessary computation).

like image 20
Anakhand Avatar answered Feb 16 '23 01:02

Anakhand