julia introspection - получить имя переменной, переданной функции
В Юлии, есть ли способ получить имя переданного функции?
x = 10
function myfunc(a)
# do something here
end
assert(myfunc(x) == "x")
Нужно ли использовать макросы или есть собственный метод, который обеспечивает самоанализ?
3 ответа
Ладно, я не согласен: технически это возможно очень хакерским способом при одном (довольно ограничивающем) условии: имя функции должно иметь только одну сигнатуру метода. По идее очень похожи ответы на такие вопросы по Python. Перед демонстрацией я должен подчеркнуть, что это внутренние детали компилятора и могут быть изменены. Кратко:
julia> function foo(x)
bt = backtrace()
fobj = eval(current_module(), symbol(Profile.lookup(bt[3]).func))
Base.arg_decl_parts(fobj.env.defs)[2][1][1]
end
foo (generic function with 1 method)
julia> foo(1)
"x"
Позвольте мне еще раз подчеркнуть, что это плохая идея и не должна использоваться ни для чего! (ну, кроме отображения обратной трассировки). По сути, это "глупые трюки с компилятором", но я показываю это, потому что играть с этими объектами может быть полезно, и объяснение приводит к более полезному ответу на поясняющий комментарий @ejang.
Объяснение:
bt = backtrace()
генерирует... backtrace... из текущей позиции.bt
это массив указателей, где каждый указатель является адресом кадра в текущем стеке вызовов.Profile.lookup(bt[3])
возвращаетLineInfo
объект с именем функции (и несколько других деталей о каждом кадре). Обратите внимание, чтоbt[1]
а такжеbt[2]
находятся в самой функции генерации обратной трассировки, поэтому нам нужно пойти дальше вверх по стеку, чтобы получить вызывающего.Profile.lookup(...).func
возвращает имя функции (символ:foo
)eval(current_module(), Profile.lookup(...))
возвращает объект функции, связанный с именем:foo
вcurrent_module()
, Если мы изменим определениеfunction foo
возвращатьfobj
затем обратите внимание на эквивалентностьfoo
Объект в REPL:julia> function foo(x) bt = backtrace() fobj = eval(current_module(), symbol(Profile.lookup(bt[3]).func)) end foo (generic function with 1 method) julia> foo(1) == foo true
fobj.env.defs
возвращает первоеMethod
запись отMethodTable
заfoo
/fobj
Base.decl_arg_parts
является вспомогательной функцией (определяется вmethodshow.jl
) который извлекает информацию об аргументе из данногоMethod
,- остальная часть индексации доходит до имени аргумента.
Что касается ограничения на то, что функция имеет только одну сигнатуру метода, то причина в том, что все сигнатуры будут перечислены в списке (см. defs.next
) в MethodTable
, Насколько я знаю, в настоящее время нет открытого интерфейса для получения конкретного метода, связанного с данным адресом кадра. (в качестве упражнения для продвинутого читателя: один из способов сделать это - изменить функциональность поиска адреса в jl_getFunctionInfo
также вернуть искаженное имя функции, которое затем может быть повторно связано с вызовом определенного метода; однако, я не думаю, что в настоящее время мы храним обратное отображение из искаженного имени -> метод).
Также обратите внимание, что (1) обратные трассировки медленные (2) в Julia нет понятия "функция-локальный" eval, поэтому даже если у кого-то есть имя переменной, я считаю, что на самом деле было бы невозможно получить доступ к переменной (и компилятору) может полностью исключить локальные переменные, неиспользуемые или нет, поместить их в регистр и т. д.)
Что касается использования самоанализа в стиле IDE, упомянутого в комментариях: foo.env.defs
как показано выше, это одно из мест, где можно начать "самоанализ объекта". Со стороны отладки Gallium.jl может проверять информацию локальной переменной DWARF в заданном кадре. И, наконец, JuliaParser.jl - это чистая реализация Julia синтаксического анализатора Julia, который активно используется в нескольких средах IDE для анализа блоков кода на высоком уровне.
Вы можете получить имя переменной с помощью макроса:
julia> macro mymacro(arg)
string(arg)
end
julia> @mymacro(x)
"x"
julia> @assert(@mymacro(x) == "x")
но, как говорили другие, я не уверен, зачем тебе это нужно.
Макросы работают с AST (деревом кодов) во время компиляции, а x
передается в макрос как символ :x
, Вы можете превратить символ в строку и наоборот. Макросы заменяют код на код, поэтому @mymacro(x)
просто вытащен и заменен string(:x)
,
Другой метод заключается в использовании функции vinfo
, Вот пример:
function test(argx::Int64)
vinfo = code_lowered(test,(Int64,))
string(vinfo[1].args[1][1])
end
test (generic function with 1 method)
julia> test(10)
"argx"
Вышесказанное зависит от знания сигнатуры функции, но это не проблема, если она закодирована внутри самой функции (в противном случае может потребоваться некоторая макромагия).