Till Hoffmann
Till Hoffmann

Reputation: 9877

unbound method <method> must be called with <class> instance as first argument

I would like to provide default behaviour for a class as illustrated below.

import numpy as np

class Test:
    def __init__(self, my_method=None):
        self.my_method = my_method or np.min

Test().my_method([1, 2, 3]) # >>> 1

The code works as expected. To keep all the default values together for easier code maintenance I wanted to change the code to

import numpy as np

class Test:
    default_method = np.min

    def __init__(self, my_method=None):
        self.my_method = my_method or Test.default_method

Test().my_method([1, 2, 3]) # >>> TypeError

but the call to my_method fails with the error message unbound method amin() must be called with Test instance as first argument (got list instance instead). Oddly, the code works as expected if I use the builtin min rather than np.min, i.e. the following works as expected.

import numpy as np

class Test:
    default_method = min # no np.

    def __init__(self, my_method=None):
        self.my_method = my_method or Test.default_method

Test().my_method([1, 2, 3]) # >>> 1

What am I missing?

Upvotes: 1

Views: 908

Answers (1)

Blckknght
Blckknght

Reputation: 104712

Any function stored as an attribute on a class object is treated as a method by Python. On Python 2, that means it requires the first argument to be an instance of the class (which will be passed automatically if the attribute is requested via an instance). On Python 3, unbound methods no longer check their arguments in that way (so your code would work as written).

To work around the issue on Python 2, try wrapping the default_method value with staticmethod:

class Test(object):
    default_method = staticmethod(np.min)
    #...

This might not be a bad idea even on Python 3, since you'll also be able to use self.default_method rather than explicitly naming the class.

As for why the code worked with min but not np.min, that's because they are implemented differently. You can see that from their types:

>>> type(min)
<class 'builtin_function_or_method'>
>>> type(np.min)
<class 'function'>

Regular functions (like np.min) act as descriptors when they're attributes of a class (thus getting the "binding" behavior that was causing your issue). Builtin functions like min don't support the descriptor protocol, so the issue doesn't come up.

Upvotes: 2

Related Questions