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

Question:

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

??

Asked By: Dan Passaro

||

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.

Answered By: Martijn Pieters

This is what I came up with… According to comments in correct answer, valid for 2.x only

def is_unbound_method(func):
    """Test if ``func`` is an unbound method.

    >>> def f(): pass
    >>> class MyClass(object):
    ...     def f(self): pass
    >>> is_unbound_method(f)
    False
    >>> is_unbound_method(MyClass().f)
    False
    >>> is_unbound_method(MyClass.f)
    True

    """
    return getattr(func, 'im_self', False) is None
Answered By: Dan Passaro

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).

Answered By: Anakhand

I know that this is a very old question, but it shows up in Google, so I will add another way: you can take a look at functions __qualname__ attribute.

Here is my code:

class Test:
    def method(self, x: int):
        print(self, x)


def free_func(x: int):
    print("I'm a func!", x)


def top_func():
    def local_func(x: int):
        print("I'm a func!", x)
    show_contents(local_func)


def show_contents(obj):
    print(getattr(obj, '__name__', ''))
    print(getattr(obj, '__qualname__', ''))


t = Test()
print('--- Instance ---')
show_contents(t.method)
print('--- Class ---')
show_contents(Test.method)
print('--- Function ---')
show_contents(free_func)
print('--- Nested Function ---')
top_func()

And here is the output:

--- Instance ---
method
Test.method
--- Class ---
method
Test.method
--- Function ---
free_func
free_func
--- Nested Function ---
local_func
top_func.<locals>.local_func

It’s more than a little hacky to use, but __qualname__ has been available since 3.3, so it should work, at least.

So you could use a function like this:

def is_method(func) -> bool:
    if not callable(func):
        raise ValueError(f"{func!r} is not a callable object")
    qualname = func.__qualname__
    name = func.__name__
    if qualname == name:  # it's a top-level function
        return False
    elif qualname.endswith('.'+name):  # it's either a nested function or a method
        prefix = qualname[:-len(name) - 1]
        return not prefix.endswith('<locals>')
    else:  # what is it, even?
        raise ValueError(f"Can't tell if {func!r} is a method")
Answered By: Vindicar
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.