Можно ли записать объект фрейма Python, возвращаемый sys._getframe() из кода Python, выполняемого в интерпретаторе?
В связи с этим вопросом, в интерпретаторе есть немного леса для проверки объектов фрейма, которые могут быть получены с помощью sys._getframe()
, Похоже, что объекты фрейма доступны только для чтения, но я не могу найти ничего очевидного в документах, где это прямо указано. Может ли кто-нибудь подтвердить, доступны ли эти объекты для записи (каким-либо образом) или только для чтения?
import sys
def foobar():
xx='foo'
ff = sys._getframe()
ff.f_locals['xx'] = 'bar'
print xx
if __name__ == '__main__':
foobar()
Это печатаетfoo
'при запуске, но пост ниже демонстрирует, что переменная доступна для записи при запуске из текущего кадра в интерактивной оболочке.
2 ответа
Из исходного кода CPython, Objects/frameobject.c
:
static PyMemberDef frame_memberlist[] = {
{"f_back", T_OBJECT, OFF(f_back), RO},
{"f_code", T_OBJECT, OFF(f_code), RO},
{"f_builtins", T_OBJECT, OFF(f_builtins),RO},
{"f_globals", T_OBJECT, OFF(f_globals), RO},
{"f_lasti", T_INT, OFF(f_lasti), RO},
{"f_exc_type", T_OBJECT, OFF(f_exc_type)},
{"f_exc_value", T_OBJECT, OFF(f_exc_value)},
{"f_exc_traceback", T_OBJECT, OFF(f_exc_traceback)},
{NULL} /* Sentinel */
};
...
static PyGetSetDef frame_getsetlist[] = {
{"f_locals", (getter)frame_getlocals, NULL, NULL},
{"f_lineno", (getter)frame_getlineno,
(setter)frame_setlineno, NULL},
{"f_trace", (getter)frame_gettrace, (setter)frame_settrace, NULL},
{"f_restricted",(getter)frame_getrestricted,NULL, NULL},
{0}
};
Для PyMemberDef
, флаги RO
или же READONLY
означает, что его атрибуты доступны только для чтения. Для PyGetSetDef
, если он имеет только геттер, он только для чтения. Это означает, что все атрибуты, кроме f_exc_type
, f_exc_value
, f_exc_traceback
а также f_trace
только для чтения после создания. Это также упоминается в документах в разделе Модель данных.
Объекты, на которые ссылаются атрибуты, не обязательно доступны только для чтения. Вы могли бы сделать это:
>>> f = sys._getframe()
>>> f.f_locals['foo'] = 3
>>> foo
3
>>>
Хотя это работает в интерпретаторе, оно не работает внутри функций. Механизм выполнения использует отдельный массив для локальных переменных (f_fastlocals
), который объединен в f_locals
на доступе, но обратное неверно.
>>> def foo():
... x = 3
... f = sys._getframe()
... print f.f_locals['x']
... x = 4
... print f.f_locals['x']
... d = f.f_locals
... x = 5
... print d['x']
... f.f_locals
... print d['x']
...
>>> foo()
3
4
4
5
>>>
На глобальной раме, f_local
относится к f_globals
, что заставляет этот трюк работать в интерпретаторе. Изменение f_globals
работает, но влияет на весь модуль.
Пример f_locals['foo'] от NXC работает, потому что код находится в области видимости модуля. В этом случае f_locals - это f_globals, а f_globals - модифицируемый, и модификации отражаются в модуле.
Внутри области действия функции locals() и f_locals доступны для записи, но "[изменения могут не повлиять на значения локальных переменных, используемых интерпретатором]". 1 Это выбор реализации. В CPython есть оптимизированный байт-код для локальных переменных, LOAD_FAST. В Python локальные переменные (почти всегда) известны после определения функции, и CPython использует поиск по индексу для получения значения переменной, а не поиск по словарю.
Теоретически поиск по словарю может проксировать эту таблицу, но это большая работа для небольшого выигрыша.
Исключения из "локальных переменных известны", если функция использует оператор exec, и не рекомендуется использовать "из модуля import *". Сгенерированный байт-код отличается и медленнее для этих случаев.