2013-07-11 2 views
2

Я должен предисловие к этому, сказав следующее: я знаю, что эта функциональность не поддерживается по умолчанию - то, что я пытаюсь, является хакерским обходным решением, которое имеет очень мало практического применения и является полная практика психической мастурбации в результате скуки и любопытства. Тем не менее, я пытаюсь сделать следующее:Получение имени локальной переменной во время выполнения в Python

Основываясь на следующем коде Python,

with BuildFile('mybuild.build') as buildfile: 
    objdir = 'obj' 

Я хотел бы создать файл, mybuild.build с содержимым:

objdir = obj 

В идеале я хотел бы связать имя переменной в точке создания, так что, если бы я должен был установить точку останова сразу после objdir = 'obj', я хотел бы иметь возможность сделать следующее:

>>> print repr(objdir) 
'objdir = obj' 

Это было бы невозможно со встроенной функциональностью, так как невозможно переопределить тип, выводимый из синтаксиса. Я могу в конечном итоге взломать обходной путь в методе BuildFile.__enter__, который использует ctypes для патча обезьяны tp_new или tp_dict полей в базовой структуре PyTypeObject (и впоследствии возвратит это переопределение при выходе), но для простоты давайте просто предположим, что я не связывая имя переменной до тех пор, пока не достигнет метода BuildFile.__exit__.

Что мне интересно, о является следующее:

Есть ли встроенные функциональные возможности Python для остановки выполнения, прослеживая назад в кадре, в котором была объявлена ​​локальная переменная, и получить локальное имя, связанное с переменной?

+0

Что вы подразумеваете под «прекращением исполнения»? Отладчик может остановить выполнение скрипта, но если сценарий останавливает выполнение ... он больше не работает, поэтому он ничего не может сделать. – abarnert

+0

Да, «прекращение исполнения» было очень плохим (и неправильным) выбором слов. Я хотел сказать, что это «ориентир нынешней структуры» с целью установления того, где искать относительно этой позиции. –

+0

ОК, да, это именно то, что делает 'sys._getframe()'. Вы даже можете сохранить объект фрейма позже и использовать его извне фрейма (хотя обычно за счет создания множества круговых ссылок для GC для очистки позже). Например, '[sys._getframe() для _ в диапазоне (1)] [0] .f_code' позволяет вам получить скрытую функцию внутри CPython listcomps, и я не могу придумать более чистый способ сделать это. – abarnert

ответ

1

У Python нет переносного способа трассировки кадров ... но реализация CPython делает: sys._getframe возвращает вам объект фрейма.

Что вы можете сделать с объектом рамки? Посмотрите удобную диаграмму в документах inspect для всех интересных вещей, которые у нее есть, но они включают locals() и globals(), как видно из фрейма, и объект кода, выполненный в фрейме, который сам включает в себя локальные имена, несвязанные имена и ячейки для затворы.

Но, как указывают другие, вам не нужна рамка для этого; все, что вам нужно, это местные жители, и гораздо проще просто передать его вашему менеджеру контекста явно.


Если вы действительно хотите сделать это:

import contextlib 
import sys 

@contextlib.contextmanager 
def dumping(): 
    f = sys._getframe(2) 
    fl = f.f_locals.copy() 
    try: 
     yield None 
    finally: 
     for name, value in f.f_locals.items(): 
      if name not in fl: 
       print('{} = {}'.format(name, value)) 

bar = 0 
def foo(): 
    global bar 
    bar = 3 
    baz = 4 
    qux = 5 
    with dumping(): 
     spam = 'eggs' 
     eggs = 3 
     bar = 4 
     baz = 5 

foo() 

При запуске, это должно напечатать:

eggs = 3 
spam = eggs 

Другими словами, имена и значения только новых переменных, были объявлены в блоке with, что, я думаю, вы хотели, правильно?


Если вы хотите, как новые, так и отбой местных жителей, вы, вероятно, хотите сохранить что-то вроде этого:

fl = {(name, id(value)) for name, value in f.f_locals.items()} 

Конечно, вы также можете переназначить nonlocals и глобал, так что если вы заботитесь о том, что , либо глобальные глобальные пробелы (но не забудьте проверить на locals is globals для кода на уровне модуля), либо пройдите по закрытию.


Если вы используете CPython 2 (почему? Для реальных проектов, иногда имеет смысл, но чтобы узнать, как внутренние работы для удовольствия? И все же, некоторые люди до ...), тот же код будет работать. Могут быть несколько разные имена атрибутов, но вы можете угадать их, сбросив dir кадра и кода. И, очевидно, вам нужен синтаксис 2.x print.

Он также работает в PyPy, не менее 2.0b.


Если вы задаетесь вопросом, как я знал, что использовать _getframe(2) ... Я не сделал. Я был уверен, что это будет 1 или 2 кадра, возможно, 3, но какой? Так что я просто сделал это:

@contextlib.contextmanager 
def dumping(): 
    for i in range(4): 
     f = sys._getframe(i) 
     print(f.f_code.co_filename, f.f_code.co_firstlineno, f.f_lineno) 

0, конечно dumping сам; 1 - оберточная функция в contextlib.contextmanager; 2 - вызывающий кадр; 3 - верхний уровень модуля. Это очевидно, когда вы думаете об этом, но это было не очевидно, пока я не узнал ответ. :)

+0

Спасибо за подробный ответ. Как я уже упоминал в своем другом комментарии, идея перехода по кадровому маршруту была в основном основана на непонимании инструкции yield и функциональности contexlib. Исходя из этого, я, вероятно, не поеду по этому маршруту, но эта информация, безусловно, станет хорошей ссылкой на будущее. Что касается использования Python 3 более 2, Python в настоящее время является моим языком выбора, когда я хочу реализовать одну из моих идей «shits and higgles». В настоящее время C API для CPython 2 более удобен для использования в моих целях, поэтому я не чувствовал необходимости делать скачок еще. Еще раз спасибо. –

+0

@ juntalis: Да, единственное, на что вы действительно используете фрейм здесь, это добраться до местных родителей, и есть более простые способы сделать это.Между тем, когда есть разница, Python 3, как правило, более рациональный и интересный из двух, по понятным причинам ... но PyPy тоже довольно интересно исследовать, и он еще не делает 3.x, поэтому я все еще нахожусь в 2.x кишки для удовольствия иногда тоже ... – abarnert

2

На самом деле вы можете выполнить подобный трюк, как это:

>>> from contextlib import contextmanager 
>>> @contextmanager 
def override_new_vars(locs): 
    old_locals = locs.copy() 
    yield 
    new_locals_names = set(locs) - set(old_locals) 
    for name in new_locals_names: 
     locs[name] = '%s = %r' % (name, locs[name]) 


>>> with override_new_vars(locals()): 
    c = 10 


>>> c 
'c = 10' 

В вашем случае это будет выглядеть так:

with BuildFile('mybuild.build') as buildfile, override_new_vars(locals()): 
    objdir = 'obj' 

Это то, что вы хотите сделать?

0

Я думаю, вы можете попробовать это:

from copy import copy 
objdir = 'obj' 

def get_variable_name(variable): 

    if variable: 
     variables = copy(locals()) 
     for i in variables: 
      if variables.get(i) == variable: 
       res = i + '=' + variables.get(i) 
       return res 

Используйте это следует сделать трюк, я тест этот код сам.

Смежные вопросы