Подтвердить что ты не робот

Добавление макросов в Python

Я хотел бы вызвать следующий код in-situ, где я ссылаюсь на MY_MACRO в моем коде ниже.

# MY_MACRO
frameinfo = getframeinfo(currentframe())
msg = 'We are on file ' + frameinfo.filename + ' and line ' +  str(frameinfo.lineno)
# Assumes access to namespace and the variables in which `MY_MACRO` is called. 
current_state = locals().items()

Вот код, который использовал бы MY_MACRO:

def some_function:
    MY_MACRO

def some_other_function:
    some_function()
    MY_MACRO

class some_class:
  def some_method:
     MY_MACRO

В случае, если это помогает:

  • Одной из причин, почему я хотел бы иметь эту способность, является то, что я хотел бы избежать повторения кода MY_MACRO везде, где он мне нужен. Наличие чего-то короткого и легкого было бы очень полезно.
  • Другая причина заключается в том, что я хочу встроить оболочку IPython в макрос, и я хотел бы иметь доступ ко всем переменным в locals().items() (см. этот другой вопрос)

Возможно ли это в Python? Каким будет самый простой способ заставить это работать?

Пожалуйста, ПРИМЕЧАНИЕ, что макрос предполагает доступ ко всему пространству имен области, в которой он вызвал (т.е. просто помещая код MY_MACRO в функцию не работает). Также обратите внимание, что если я помещаю MY_MACRO в функцию, lineno выводит неверный номер строки.

4b9b3361

Ответ 1

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

С другой стороны, у python есть некоторые довольно удивительные возможности самоанализа, поэтому я подозреваю, что вы сможете выполнить то, что хотите, используя эту функциональность.

Ответ 2

Как насчет функции, которую вы можете вызвать? Эта функция обращается к кадру вызывающего абонента и вместо использования locals() использует frame.f_locals для получения пространства имен вызывающего абонента.

def my_function():
    frame = currentframe().f_back
    msg = 'We are on file {0.f_code.co_filename} and line {0.f_lineno}'.format(frame)
    current_state = frame.f_locals
    print current_state['some_variable']

Затем просто назовите его:

def some_function:
    my_function()

def some_other_function:
    some_function()
    my_function()

class some_class:
  def some_method:
     my_function()

Ответ 3

Использование exec неодобрительно, но это должно сделать трюк. Например, возьмите следующий макрос:

MY_MACRO = """
print foo            
"""

и запустите его, используя следующий код:

foo = "breaking the rules"
exec MY_MACRO in globals(),locals() 

Всегда будьте осторожны с exec, потому что он может иметь странные побочные эффекты и открывает возможности для ввода кода.

Ответ 4

вы можете использовать функцию, если хотите:

def MY_MACRO():
    frame = currentframe()
    try:
        macro_caller_locals = frame.f_back.f_locals
        print(macro_caller_locals['a'])

    finally:
        del frame

def some_function:
    a = 1
    MY_MACRO()

Ответ 5

Я не уверен, что это хорошее решение, но, по крайней мере, стоит рассмотреть препроцессор макросов.

Существует несколько проектов с расширенным расширением Python с макросами или более широкие проекты, которые должны сделать это проще, но у меня есть только истекшие ссылки для всех (Logix, MetaPython, Mython, Espy)... Возможно, стоит искать текущие ссылки и/или новые/печные проекты.

Вы можете использовать что-то вроде m4 или cpp, или что-то более мощное, или даже создать его самостоятельно. Но на самом деле, вы только что получили небольшой статический набор (пока) одного из чисто текстовых макросов. В худшем случае вам нужно определить уровень отступов MY_MACRO и добавить это к началу каждой строки, что тривиально делать в регулярном выражении. Значение sed, или трехстрочный Python script, может быть вашим препроцессором.

Однако есть две проблемы или, по крайней мере, досады.

Сначала вам нужно предварительно обработать файлы. Если вы уже используете модули расширения C или сгенерированный код или любой другой код, который вам нужен, setup.py (или make или scons или что-то еще), прежде чем вы сможете его запустить, или вы используете среду IDE, где вы просто нажмите cmd-R или ctrl-shift-B или что-то еще, чтобы протестировать ваш код, это не проблема. Но для типичного цикла редактирования-теста с текстовым редактором в одном окне и интерактивного интерпретатора в другом... ну, вы только что превратили его в цикл редактирования-компиляции-теста. Тьфу. Единственное решение, о котором я могу думать, - это захват импорта, который препроцессирует каждый файл перед импортом его в виде модуля, который, похоже, требует большой работы для небольшого преимущества.

Во-вторых, ваши номера строк и источник (из MY_MACRO, а также из tracebacks и inspect.getsource и т.д.) будут номерами строк предварительно обработанных файлов, а не исходным исходным кодом, который вы открыли для редактирования. Поскольку ваши предварительно обработанные файлы довольно читабельны, это не страшно (не так плохо, как кодирование CoffeeScript и отладка его как JavaScript, что большая часть сообщества CoffeeScript делает каждый день...), но это определенно раздражает.

Конечно, одним из способов решения этого является создание собственного макропроцессора в интерпретаторе на любом этапе процесса разбора/компиляции, который вы хотите. Я предполагаю, что гораздо больше работы, чем вы хотите, но если вы это сделаете, то Гвидо всегда предпочитает иметь фактический рабочий дизайн и реализацию, чтобы отказаться, вместо того, чтобы продолжать отклонять неопределенные предложения "Эй, пусть добавляет макросов на Python".:)

Ответ 6

Если вам нужно только имя строки и функции вызывающего абонента, как мне было нужно для отладки, вы можете получить информацию о функции звонящего по ссылке inspect.getouterframes .

import inspect
def printDebugInfo():
  (frame,filename,line_number,function_name, lines, 
    index) = inspect.getouterframes(inspect.currentframe())[1]
  print(filename, line_number, function_name, lines, index)

def f1():
  printDebugInfo()

if __name__=='__main__':
  f1()

Ответ 7

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

def print_frame_info(frameinfo, locals):
    msg = 'We are on file ' + frameinfo.filename + ' and line ' +  str(frameinfo.lineno)
    current_state = locals.items()

def some_other_function:
    some_function()
    print_frame_info(currentframe(), locals())