Search code examples
pythonclass-method

How does assignment of a function as a class attribute become a method in Python?


>>> class A(object): pass
>>> def func(cls): pass
>>> A.func = func
>>> A.func
<unbound method A.func>

How does this assignment create a method? It seems unintuitive that assignment does the following for classes:

  • Turn functions into unbound instance methods
  • Turn functions wrapped in classmethod() into class methods (actually, this is pretty intuitive)
  • Turn functions wrapped in staticmethod() into functions

It seems that for the first, there should be an instancemethod(), and for the last one, there shouldn't be a wrapper function at all. I understand that these are for uses within a class block, but why should they apply outside of it?

But more importantly, how exactly does assignment of the function into a class work? What magic happens that resolves those 3 things?

Even more confusing with this:

>>> A.func
<unbound method A.func>
>>> A.__dict__['func']
<function func at 0x...>

But I think this is something to do with descriptors, when retrieving attributes. I don't think it has much to do with the setting of attributes here.


Solution

  • You're right that this has something to do with descriptor protocol. Descriptors are how passing the receiver object as the first parameter of a method is implemented in Python. You can read more detail about Python attribute lookup from here. The following shows on a bit lower level, what is happening when you do A.func = func; A.func:

    # A.func = func
    A.__dict__['func'] = func # This just sets the attribute
    # A.func
    #   The __getattribute__ method of a type object calls the __get__ method with
    #   None as the first parameter and the type as the second.
    A.__dict__['func'].__get__(None, A) # The __get__ method of a function object
                                        # returns an unbound method object if the
                                        # first parameter is None.
    a = A()
    # a.func()
    #   The __getattribute__ method of object finds an attribute on the type object
    #   and calls the __get__ method of it with the instance as its first parameter.
    a.__class__.__dict__['func'].__get__(a, a.__class__)
    #   This returns a bound method object that is actually just a proxy for
    #   inserting the object as the first parameter to the function call.
    

    So it's the looking up of the function on a class or an instance that turns it into a method, not assigning it to a class attribute.

    classmethod and staticmethod are just slightly different descriptors, classmethod returning a bound method object bound to a type object and staticmethod just returns the original function.