Reputation: 803
I would like to understand/find out in which order chained numpy
statements are evaluated. For example:
x = A.dot(B).dot(C)
Is it (A.dot(B)).dot(C)
or A.dot(B.dot(C))
or even (A.dot(C)).dot(B)
?
Is there any way to see what happens in memory, what the interpreter's execution plan is or is there a general rule?
Upvotes: 1
Views: 199
Reputation: 1079
Please always give a reproducible example so that others can easily try things out on their computers. I do not know what A
, B
, or C
are.
When methods are chained as you have it, the .dot()
applies to the expression that immediately precedes it. You have A.dot(B).dot(C)
, so the .dot(B)
applies to A, and .dot(C)
applies to the result of A.dot(B)
. In other words, it is equivalent to (A.dot(B)).dot(C)
. In order to see what the interpreter is doing, try the ast
module:
import ast
ast.dump(ast.parse("""A.dot(B).dot(C)"""))
and with some formatting, you get
Module(
body=[
Expr(
value=Call(
func=Attribute(
value=Call(
func=Attribute(
value=Name(id='A', ctx=Load()),
attr='dot',
ctx=Load()
),
args=[Name(id='B', ctx=Load())],
keywords=[]
),
attr='dot',
ctx=Load()
),
args=[Name(id='C', ctx=Load())], keywords=[])
)
], type_ignores=[]
)
(note in Python 3.9, you can use the indent
keyword on ast.dump
to do the formatting automatically, i.e. ast.dump(ast.parse("""A.dot(B).dot(C)"""), indent=4)
).
From the parsed version, you can see the inner most code is calling A.dot(B)
first, then applying .dot(C)
to that result.
To remove ambiguity, you can use np.dot()
instead, i.e.
np.dot(np.dot(A, B), C)
to move from infix to prefix notation, as it were, to remove the ambiguity you see.
See the docs for numpy's dot
here.
Upvotes: 1