Можно ли записать объект фрейма Python, возвращаемый sys._getframe () из кода Python, выполняемого в интерпретаторе? - PullRequest
1 голос
/ 09 марта 2009

По поводу В этом вопросе , в интерпретаторе есть немного лесов для проверки объектов фрейма, которые могут быть получены с помощью sys._getframe(). Похоже, что объекты фрейма доступны только для чтения, но я не могу найти ничего очевидного в документах, где это прямо указано. Может ли кто-нибудь подтвердить, доступны ли эти объекты для записи (каким-либо образом) или только для чтения?

import sys

def foobar():
    xx='foo'
    ff = sys._getframe()
    ff.f_locals['xx'] = 'bar'
    print xx

if __name__ == '__main__':
    foobar()

Это выдает 'foo' при запуске, но пост ниже демонстрирует возможность записи переменной при запуске из текущего кадра в интерактивной оболочке.

Ответы [ 2 ]

10 голосов
/ 09 марта 2009

Из источника 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 работает, но влияет на весь модуль.

1 голос
/ 09 марта 2009

Пример f_locals ['foo'] от NXC работает, потому что код находится в области видимости модуля. В этом случае f_locals - это f_globals, а f_globals - модифицируемый, и модификации отражаются в модуле.

Внутри области действия функции, localals () и f_locals доступны для записи, но «[изменения могут не повлиять на значения локальных переменных, используемых интерпретатором]». 1 Это выбор реализации. В CPython есть оптимизированный байт-код для локальных переменных, LOAD_FAST. В Python локальные переменные (почти всегда) известны после определения функции, а CPython использует поиск по индексу для получения значения переменной, а не поиск по словарю.

Теоретически поиск по словарю может проксировать эту таблицу, но это большая работа для небольшого выигрыша.

Исключения из «локальных переменных известны», если функция использует оператор exec, и устаревший случай «из импорта модуля *». Сгенерированный байт-код отличается и медленнее для этих случаев.

...