[Python] Functions, Methods & Attributes!

First, lets take a look at functions & methods and then define attributes for them.

Functions Vs Methods:

In [2]: def f():
   ...:     pass
   ...: 

In [3]: class C:
   ...:     def m(self):
   ...:         pass
   ...:
We have just defined a function and a class(with a method in it).
In [18]: type(f)
Out[18]: function

In [19]: type(C.m)
Out[19]: function

In [20]: type(C().m)
Out[20]: method

In [21]: set(dir(C().m)) - set(dir(f))
Out[21]: {'__func__', '__self__'}
As seen above, a function binded to an instance of a class is method and an unbound method is a just a function. Also a method has __self__ and __func__ attributes in addition attributes of a function.

Attributes:

Lets add some attributes to function & method and see how they work.
In [21]: setattr(f, 'state', 1)

In [22]: hasattr(f, 'state')
Out[22]: True

In [24]: getattr(f, 'state')
Out[24]: 1
We can do the same thing with unbound method(which is nothing but a function) also
In [31]: setattr(C.m, 'state', 2)

In [32]: hasattr(C.m, 'state')
Out[32]: True

In [33]: getattr(C.m, 'state')
Out[33]: 2
But we cant do the same thing with bound methods.
In [34]: setattr(C().m, 'state', 3)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-34-1379e94f2f48> in <module>()
----> 1 setattr(C().m, 'state', 2)

AttributeError: 'method' object has no attribute 'state'
When users add attributes to a function, they are stored in its __dict__ attribute.
In [40]: f.__dict__
Out[40]: {'state': 1}

In [49]: C.m.__dict__
Out[49]: {'state': 2}
As we have seen in the beginning, method objects just hold reference to its class(__self__) and function(__func__). But they don't have its own __dict__ to hold custom attributes. So we cannot set custom attributes to instance methods.
But we can get the function that is referenced by bound method and set attribute for it.
In [36]: setattr(C().m.__func__, 'state', 3)

In [39]: getattr(C().m.__func__, 'state')
Out[39]: 3
Also methods provide, special __getattr__ which forwards attribute access to function object. So, this will work
In [52]: hasattr(C().m, 'state')
Out[52]: True

In [53]: getattr(C().m, 'state')
Out[53]: 3
So we can just set attributes to functions & unbound methods just like classes but we can't do it for bound methods.