Hi Stephen,
You ask:
“Why are these method-wrappers created?”
You are looking up attributes of objects, so the attribute lookup
machinery gets invoked.
You have a function object, thing
. When you look up thing.__call__
that’s a runtime attribute lookup, which returns the method-wrapper
__call__
.
That method object itself is an object, so when you look up the
__call__
attribute on that, you again get a method-wrapper.
The details are related to the descriptor protocol, which you will
find explained on the Python website. The attribute lookup machinery
doesn’t make an exception for __call__
, or function objects. Any
attribute lookup goes through the same machinary.
Remember that in Python, everything is an object, including functions,
methods, and function.__call__
.
As for what purpose the method-wrapper serves, without it calling
thing.__call__
would fail unless you wrote the call as:
thing.__call__(thing)
and provided the “self” parameter by hand. Remember that the __call__
method of function objects are defined on the FunctionType class, which
means that the __call__
method needs to know which instance is being
called.
You ask:
“Why are the hex numbers the same for the original function and the
first wrapper but the ids change?”
The hex number 0x000001C6EED3F040 in your example refers back to the
memory location of the original function object each time.
The object IDs are arbitrary ID numbers. The details of whether they
change or not will depend on many factors, such as the specific version
of the interpreter, whether ID numbers can get re-used or not, how
quickly they get re-used, etc. The only rules for IDs are:
In this specific case the numbers change because you are holding onto
the method objects, preventing their IDs from being recyled. Otherwise
you likely would have seen a pattern something like:
1234567890 function thing
1234567900 thing.__call__
1234567920 thing.__call__.__call__
1234567900 thing.__call__.__call__.__call__
1234567920 thing.__call__.__call__.__call__.__call__
where the IDs cycle. Another common pattern (Jython and IronPython)
would be something like:
123 function thing
124 thing.__call__
125 thing.__call__.__call__
126 thing.__call__.__call__.__call__
127 thing.__call__.__call__.__call__.__call__
that is, consecutive ID numbers with no re-use.
You ask:
“Shouldn’t the wrapper implement __eq__ which considers id of the
originally defined function?”
For what purpose?
The default __eq__
method, inherited from the base object, compares
object identity. Without some special reason to care about comparing
methods for identity, why would we change that?