I was reading a presentation on Pythons' Object model when, in one slide (number 9), the author asserts that Pythons' functions are descriptors. The example he presents to illustrate is similar to this one I wrote:
def mul(x, y):
return x * y
mul2 = mul.__get__(2)
mul2(3) # 6
Now, I understand that the point is made, since the function defines a __get__ it is a descriptor as I described in the description section of the Python documentation.
What I don't understand is how exactly the call results in the output provided.
A descriptor is a mechanism behind properties, methods, static methods, class methods, and super() . Descriptor protocol : In other programming languages, descriptors are referred to as setter and getter, where public functions are used to Get and Set a private variable.
Descriptors are a powerful, general purpose protocol. They are the mechanism behind properties, methods, static methods, class methods, and super() . They are used throughout Python itself. Descriptors simplify the underlying C code and offer a flexible set of new tools for everyday Python programs.
There are two types of descriptors: data descriptors and non-data ones.
The Cliff's Notes version: descriptors are a low-level mechanism that lets you hook into an object's attributes being accessed. Properties are a high-level application of this; that is, properties are implemented using descriptors.
That's Python doing what it does in order to support dynamically adding functions to classes.
When __get__ is invoked on a function object (usually done via dot access . on an instance of a class) Python will transform the function to a method and implicitly pass the instance (usually recognized as self) as the first argument.
In your case, you explicitly call __get__ and explicitly pass the 'instance' 2 which is bound as the first argument of the function x, here 2 is considered the "instance" self:
>>> mul2
<bound method mul of 2>
This results in a method bound on the instance 2, with one expected argument that yields the multiplication: calling it returns 2 (the bound argument assigned to x) multiplied with anything else you supply as the argument y.
Normally, function() invokes it's __call__ with the appropriate arguments provided:
mul.__call__(2, 3) # 6
As a plus, a Python implementation of __get__ for functions is provided in the Descriptor HOWTO document of the Python Docs.
Here you can see the transformation, with the usage of types.MethodType, that takes place when __get__ is invoked :
class Function(object):
. . .
def __get__(self, obj, objtype=None):
"Simulate func_descr_get() in Objects/funcobject.c"
return types.MethodType(self, obj, objtype)
And the source code for the intrigued visitor is located in Objects/funcobject.c.
As you can see if this descriptor did not exist you'd have to automatically wrap functions in types.MethodType any time you'd want to dynamically add a function to class which is an unnecessary hassle.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With