Добавление макросов в 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
выводит неверный номер строки.
Ответы
Ответ 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())