Программно/динамически импортировать модули в Julia

Мне было интересно, есть ли способ программно или динамически импортировать набор модулей в Джулию? Например, если у меня есть список файлов, которые соответствуют определенному соглашению об именах, которые присутствуют при запуске, я могу захватить с помощью чего-то вроде:

module_files = filter(r"^mod[0-9][0-9].jl$", readdir())

который может вернуть список файлов ["mod00.jl", "mod02.jl", "mod05.jl"], есть способ затем импортировать каждый из модулей в эти файлы. Это было бы эквивалентно:

import mod00
import mod02
import mod05

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

Обновить

Я попытался сделать это с помощью макроса, но не повезло. Например:

macro import_mod(modn)
    quote
        import $modn
    end
end

function test_mimport()
    module_files = filter(r"^mod[0-9][0-9].jl$", readdir())
    println(module_files)
    for modname in factor_files
        modn = modname[1:end-3]
        println(modn)
        @import_mod modn
    end
end

Когда я запускаю это, я получаю ERROR: syntax: invalid "import" statement. Я пробовал различные стратегии побега, но все так не получилось.

Ответы

Ответ 1

Функциональная версия import X require("X"), поэтому вы должны иметь возможность:

function test_mimport()
    module_files = filter(r"^mod[0-9][0-9].jl$", readdir())
    println(module_files)
    for modname in factor_files
        modn = modname[1:end-3]
        println(modn)
        require(modn)
    end
end

Затем, предполагая, что каждый из них определяет модуль с тем же именем, вы можете собрать их в массив:

modules = Module[]
...
if isdefined(Main, symbol(modn))
    push!(modules, getfield(Main, symbol(modn))
else
    warn("importing $modn did not defined a module")
end
...
return modules

Ответ 2

Примечание: Пожалуйста, обратитесь к user1712368 (Jameson Nash, Джулия Dev) ответ, это обсуждение в Жюлиа списке рассылки, и эта запись о Джулии инструкции, чтобы узнать о том, почему это не правильный ответ.

Вот как выглядит выражение множественного импорта, используя версию Julia 0.3.4:

julia> parse("import Foo, Bar")
:($(Expr(:toplevel, :($(Expr(:import, :Foo))), :($(Expr(:import, :Bar))))))

julia> dump(ans)
Expr 
  head: Symbol toplevel
  args: Array(Any,(2,))
    1: Expr 
      head: Symbol import
      args: Array(Any,(1,))
        1: Symbol Foo
      typ: Any
    2: Expr 
      head: Symbol import
      args: Array(Any,(1,))
        1: Symbol Bar
      typ: Any
  typ: Any

Вот макрос, который делает это программно, он принимает аргумент modules который может быть :call или :vcat Expr или Symbol, который должен быть оценен с помощью Vector{Symbol}:

julia> macro dynamic_import(modules)
           (modules = eval(modules))::Vector{Symbol}
           ex = Expr(:toplevel)
           for m in modules
               push!(ex.args, Expr(:import, m))
           end
           return ex
       end

Вы также можете обобщить эту строку:

module_files = filter(r"^mod[0-9][0-9].jl$", readdir())

Абстрагируя его на функцию, которая принимает путь Regex и String качестве аргументов и возвращает Vector{Symbol}:

julia> function needed_modules(rx::Regex, dir::String=".")
           module_files = filter(rx, readdir(dir))
           module_syms = map(m -> symbol(split(m, '.')[1]), module_files)
       end
needed_modules (generic function with 2 methods)

Поэтому вы можете использовать его следующим образом:

julia> @dynamic_import [:Mod01, :Mod02]    # :vcat expression

julia> rx = r"^Mod[0-9][0-9].jl$";

julia> @dynamic_import needed_modules(rx)    # :call expression

julia> modules = needed_modules(rx)
2-element Array{Symbol,1}:
 :Mod01
 :Mod02

julia> @dynamic_import modules    # Symbol

Наконец, вы можете объединить все это в модуль, чтобы использовать using DynamicImport :

Примечание: в настоящее время я получаю это при попытке запустить те же примеры из модуля:

julia> using DynamicImport

julia> @dynamic_import [:mod01, :mod02]

julia> rx = r"^mod[0-9][0-9].jl$";

julia> @dynamic_import needed_modules(rx)
ERROR: rx not defined

julia> modules = needed_modules(rx)
2-element Array{Symbol,1}:
 :mod01
 :mod02

julia> @dynamic_import modules
ERROR: modules not defined

Но он отлично работает. Если я определяю объекты внутри REPL, я думаю, что это проблема, связанная с гигиеной, с которой я не сталкиваюсь, поэтому я попрошу в списке рассылки julia-users.

Ответ 3

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

function test_mimport()
    module_files = filter(r"^mod[0-9][0-9].jl$", readdir())
    path, f = mktemp()
    for modname in module_files
        modn = splitext(modname)[1]
        write(f, "import $modn\n")
    end

    close(f)
    include(path)
    rm(path)

end

Мне было бы интересно узнать, есть ли более идиоматическая стратегия, так как я новичок в Джулии и просто ватин в своих возможностях.

редактировать

Стратегия создания временного файла, вероятно, не нужна, поскольку Julia также предоставляет метод include_string.

В списке пользователей Julia @Isaiah также предлагает использовать eval(Expr(:import, symbol(modn)))