On 13/12/2020 22:09, Paul Sokolovsky wrote:
Thanks for hanging with me so far, we're getting to the crux of the question:
Do you think there can be difference between the following two expressions:
obj.meth() (obj.meth)()
?
No. The value of an expression in parentheses is the value of the expression inside the parentheses, and in this case does not affect the order of evaluation.
python3.6 -m dis meth_call.py python3.7 -m dis meth_call.py
Then, to try to explain the difference at the suitable level of abstraction. If that doesn't provide enough differentiation, it might be helpful to add the 3rd line:
t = obj.meth; t()
And run all 3 lines thru CPython3.7, and see if the pattern is now visible, and a distortion in the pattern too.
What would be the explanation for all that?
The explanation is an optimisation introduced in 3.7 that the use of an intermediate variable prevents. The compiler applies it when it can see the only use of the attribute is an immediately following call. Having burrowed into the implementation, I'm certain it tries hard to be indistinguishable from the unoptimised implementation (and succeeds I think), even to the point of failing in the same way when that is the programmed outcome. LOAD_METHOD goes far enough down the execution of LOAD_ATTR to be sure the bound object would be a types.MethodType containing a pair of pointers that CALL_FUNCTION would have to unpack, and pushes the pointers on the stack instead of creating the new object. Otherwise it completes LOAD_ATTR and pushes a bound object and a NULL, which is what CALL_METHOD uses to decide which case it is dealing with. The meaning of the code is what it does detectably in Python, not what it compiles to (for some definition of "detectably" that doesn't include disassembly). Jeff Allen