开发者

Is there anything static about python function / method invocations?

开发者 https://www.devze.com 2023-02-05 23:17 出处:网络
In asking a question about reflection I asked: Nice answer. But there is a difference between saying myobject.foo() and x = getattr(myobject, \"foo\"); x();. Even if it is only cosmetic. In the firs

In asking a question about reflection I asked:

Nice answer. But there is a difference between saying myobject.foo() and x = 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:

开发者_如何学Go

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        


All invocations are treated as dictionary lookups (Well, internally python maybe doing some kind of optimization but as far as how it works, you can assume they are dictionary lookups).

There's no static compilation in python. It's possible to even do this:

t = Thing()
t.m = lambda : 1
t.m()


It depends whether you are asking about Python the language, or a specific implementation such as CPython.

The language itself doesn't say the two are identical, so it is possible that a direct attribute access could be optimised in some way. However the dynamic nature of Python makes it hard to do that consistently, so in CPython the two are effectively identical.

Having said that, a direct t.m() may be about twice as fast as using getattr() because it involves one dictionary lookup instead of the two you get with getattr(): i.e. the global name getattr() itself has to be looked up in a dictionary.


Not only classes are modifiable at runtime (as on the HS example); but even the class keyword is 'executed' at runtime:

A class definition is an executable statement. It first evaluates the inheritance list, if present. Each item in the inheritance list should evaluate to a class object or class type which allows subclassing. The class’s suite is then executed in a new execution frame (see section Naming and binding), using a newly created local namespace and the original global namespace. (Usually, the suite contains only function definitions.) When the class’s suite finishes execution, its execution frame is discarded but its local namespace is saved. [4] A class object is then created using the inheritance list for the base classes and the saved local namespace for the attribute dictionary. The class name is bound to this class object in the original local namespace.

(Python language reference 7.7: Class Definitions)

In other words, at startup time, the interpreter executes the code inside the class block and keeps the resultant context. At compile time there's no way to know how the class will look like.


With getattr you can access attributes whose names are not valid identifiers, though I'm not sure if there is a use case for using attributes like that, instead of using a dictionary.

>>> setattr(t, '3', lambda : 4)
>>> t.3()
  File "<stdin>", line 1
    t.3()
      ^
SyntaxError: invalid syntax
>>> getattr(t, '3')()
4
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号