In asking a question about reflection I asked:
Nice answer. But there is a difference between saying
myobject.foo()
andx = getattr(myobject, "foo"); x();
. Even if it is only cosmetic. In the first the foo() is compiled in statically. In the second, the string could be produced any number of ways. – Joe 1 hour ago
Which got the answer:
Eh, potato / solanum tuberosum... in python, niether is statically compiled, so they are more or less equivalent. – SWeko 1 hour ago
I know that Python objects' members are stored in a dictionary and that everything is dynamic, but I assumed that given the following code:
class Thing():
def m(self):
pass
t = Thing()
The following code would somehow get statically compiled when the .pyc is generated:
t.m()
i.e. the compiler knows the address of m()
, so no point binding at runtime. That or the runtime would cache subsequent lookups.
Whereas this would always involve hitting the dictionary:
meth = getattr(t, "m")
meth()
Are all invocations treated as string lookups in dictionaries? Or are the two examples actually identical?
They're not entirely identical, but they are both dictionary lookups, as can be shown with the disassembler dis.dis
.
In particular, note the LOAD_ATTR
instruction with dynamically looks up the attribute by name. According to the docs, it "replaces TOS [top of stack] with getattr(TOS, co_names[namei])
".
>>> from dis import dis
>>> dis(lambda: t.m())
1 0 LOAD_GLOBAL 0 (t)
3 LOAD_ATTR 1 (m)
6 CALL_FUNCTION 0
9 RETURN_VALUE
>>> dis(lambda: getattr(t, 'm')())
1 0 LOAD_GLOBAL 0 (getattr)
3 LOAD_GLOBAL 1 (t)
6 LOAD_CONST 0 ('m')
9 CALL_FUNCTION 2
12 CALL_FUNCTION 0
15 RETURN_VALUE