Есть ли что-нибудь статичное в вызовах функций / методов python?
-
13-10-2019 - |
Вопрос
В задавая вопрос о рефлексии - Спросил я:
Хороший ответ.Но есть разница между словами
myobject.foo()
иx = getattr(myobject, "foo"); x();
.Даже если это всего лишь косметика.В первом случае foo() компилируется статически.Во втором случае строка может быть создана любым количеством способов.– Джо , 1 час назад
На что получил ответ:
Эх, картофель / solanum tuberosum...в python niether компилируется статически, поэтому они более или менее эквивалентны.– SWeko 1 час назад
Я знаю, что элементы объектов Python хранятся в словаре и что все является динамическим, но я предположил, что, учитывая следующий код:
class Thing():
def m(self):
pass
t = Thing()
Следующий код каким - то образом будет статически скомпилирован при создании файла .pyc:
t.m()
т. е.компилятор знает адрес m()
, так что нет привязки точки во время выполнения.Это или среда выполнения будет кэшировать последующие запросы.
Принимая во внимание, что это всегда будет включать в себя обращение к словарю:
meth = getattr(t, "m")
meth()
Все ли вызовы обрабатываются как поиск строк в словарях?Или эти два примера на самом деле идентичны?
Решение
Они не совсем идентичны, но оба являются поиском по словарю, как можно показать с помощью дизассемблера dis.dis
.
В частности, обратите внимание на LOAD_ATTR
инструкция с помощью динамически выполняет поиск атрибута по имени.Согласно документам, он "заменяет TOS [top of stack] на 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
Другие советы
Все вызовы обрабатываются как поиск по словарю (ну, внутри python, возможно, выполняет какую-то оптимизацию, но насколько это работает, вы можете предположить, что это поиск по словарю).
В python нет статической компиляции.Возможно даже сделать это:
t = Thing()
t.m = lambda : 1
t.m()
Это зависит от того, спрашиваете ли вы о языке Python или о конкретной реализации, такой как CPython.
Сам язык не говорит, что эти два параметра идентичны, поэтому вполне возможно, что прямой доступ к атрибуту может быть каким-то образом оптимизирован.Однако динамическая природа Python затрудняет выполнение этого последовательно, поэтому в CPython они фактически идентичны.
Сказав это, прямой t.m()
может быть примерно в два раза быстрее, чем при использовании getattr()
потому что это включает в себя один поиск по словарю вместо двух, которые вы получаете с помощью getattr()
:т. е.глобальное название getattr()
само по себе должно быть просмотрено в словаре.
Не только классы могут быть изменены во время выполнения (как на Пример HS);но даже тот class
ключевое слово "выполняется" во время выполнения:
Определение класса - это исполняемый оператор .Сначала он оценивает список наследования , если таковой имеется.Каждый элемент в списке наследования должен соответствовать объекту класса или class типу, который допускает создание подклассов.Затем набор класса выполняется в новом фрейме выполнения (см. Раздел Именование и привязка), используя вновь созданное локальное пространство имен и исходное глобальное пространство имен.(Как правило, набор содержит только функции определениями.) Для класса люкс завершает выполнение, его исполнения фрейм отбрасывается, но его локальное пространство имен сохраняется.[4] Затем создается объект класса с использованием списка наследования для базовых классов и сохраненного локального пространства имен для словаря атрибутов.Имя класса привязано к этому объекту класса в исходном локальном пространстве имен.
(Справочник по языку Python 7.7:Определения классов)
Другими словами, во время запуска интерпретатор выполняет код внутри class
блокирует и сохраняет результирующий контекст.Во время компиляции нет никакого способа узнать, как будет выглядеть класс.
С getattr
вы можете получить доступ к атрибутам, имена которых не являются допустимыми идентификаторами, хотя я не уверен, есть ли вариант использования таких атрибутов вместо использования словаря.
>>> setattr(t, '3', lambda : 4)
>>> t.3()
File "<stdin>", line 1
t.3()
^
SyntaxError: invalid syntax
>>> getattr(t, '3')()
4