Можно ли записать объект фрейма 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 *". Сгенерированный байт-код отличается и медленнее для этих случаев.

Другие вопросы по тегам