Accessing the function through the class dictionary does not invoke$_{__get__()}$ . Instead, it just returns the under- lying function object: >>> D.f.__qualname__ 'D.f' >>> D.__dict__ ['f'] <function D.f at 0x00C45070> Dotted access from a class calls$_{__get__()}$ which just returns the underlying function unchanged: >>> D.f <function D.f at 0x00C45070> The interesting behavior occurs during dotted access from an instance. The dotted lookup calls __get__() which returns a bound method object: Internally, the bound method stores the underlying function and the bound instance: If you have ever wondered where self comes from in regular methods or where cls comes from in class methods, this is it! Non-data descriptors provide a simple mechanism for variations on the usual patterns of binding functions into meth- ods. To recap, functions have a$_{__get__()}$ method so that they can be converted to a method when accessed as at- tributes. The non-data descriptor transforms an$_{obj.f(*args)}$ call into$_{f(obj, *args)}$ . Calling$_{cls.}$ f(*args) becomes$_{f(*args)}$ . This chart summarizes the binding and its two most useful variants: 4.3 Kinds of methods >>> d.f.__func__ <function D.f at 0x00C45070> >>> d.f.__self__ <__main__.D object at 0x1012e1f98> >>> d =D() >>> d.f <bound method D.f of <__main__.D object at 0x00B18C90>> Transformation Called from an object Called from a class function f(obj, *args) f(*args) staticmethod f(*args) f(*args) classmethod f(type(obj), *args) f(cls, *args) 15