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"

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

Другие вопросы по тегам