>>> 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.
Point 1: The function
func
you defined exists as a First-Class Object in Python.Point 2: Classes in Python store their attributes in their
__dict__
.So what happens when you pass a function as the value of a class attribute in Python? That function is stored in the class'
__dict__
, making it a method of that class accessed by calling the attribute name you assigned it to.What you have to consider is that in Python everything is an object. By establishing that it is easier to understand what is happening. If you have a function
def foo(bar): print bar
, you can dospam = foo
and callspam(1)
, getting of course,1
.Objects in Python keep their instance attributes in a dictionary called
__dict__
with a "pointer" to other objects. As functions in Python are objects as well, they can be assigned and manipulated as simple variables, passed around to other functions, etc. Python's implementation of object orientation takes advantage of this, and treats methods as attributes, as functions that are in the__dict__
of the object.Instance methods' first parameter is always the instance object itself, generally called
self
(but this could be calledthis
orbanana
). When a method is called directly on theclass
, it is unbound to any instance, so you have to give it an instance object as the first parameter (A.func(A())
). When you call a bound function (A().func()
), the first parameter of the method,self
, is implicit, but behind the curtains Python does exactly the same as calling directly on the unbound function and passing the instance object as the first parameter.If this is understood, the fact that assigning
A.func = func
(which behind the curtains is doingA.__dict__["func"] = func
) leaves you with an unbound method, is unsurprising.In your example the
cls
indef func(cls): pass
actually what will be passed on is the instance (self
) of typeA
. When you apply theclassmethod
orstaticmethod
decorators do nothing more than take the first argument obtained during the call of the function/method, and transform it into something else, before calling the function.classmethod
takes the first argument, gets theclass
object of the instance, and passes that as the first argument to the function call, whilestaticmethod
simply discards the first parameter and calls the function without it.Descriptors are the magic1 that turns an ordinary function into a bound or unbound method when you retrieve it from an instance or class, since they’re all just functions that need different binding strategies. The
classmethod
andstaticmethod
decorators implement other binding strategies, andstaticmethod
actually just returns the raw function, which is the same behavior you get from a non-functioncallable
object.See “User-defined methods” for some gory details, but note this:
So if you wanted this transformation for your own callable object, you could just wrap it in a function, but you could also write a descriptor to implement your own binding strategy.
Here’s the
staticmethod
decorator in action, returning the underlying function when it’s accessed.Whereas a normal object with a
__call__
method doesn’t get transformed:1 The specific function is
func_descr_get
in Objects/funcobject.c.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:
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
andstaticmethod
are just slightly different descriptors, classmethod returning a bound method object bound to a type object and staticmethod just returns the original function.Relating to MTsoul's comment to Gabriel Hurley's answer:
What is different is that
func
has a__call__()
method, making it "callable", i.e. you can apply the()
operator to it. Check out the Python docs (search for__call__
on that page).