Reflection 朱莉娅:用给定的字符串调用函数

Reflection 朱莉娅:用给定的字符串调用函数,reflection,julia,Reflection,Julia,Julia是否像java一样支持反射? 我需要的是这样的东西: str = ARGS[1] # str is a string # invoke the function str() 好办法 建议的方法是将函数名转换为符号,然后在适当的命名空间中查找该符号: julia> fn = "time" "time" julia> Symbol(fn) :time julia> getfield(Main, Symbol(fn)) time (generic function w

Julia是否像java一样支持反射?

我需要的是这样的东西:

str = ARGS[1] # str is a string
# invoke the function str()
好办法 建议的方法是将函数名转换为符号,然后在适当的命名空间中查找该符号:

julia> fn = "time"
"time"

julia> Symbol(fn)
:time

julia> getfield(Main, Symbol(fn))
time (generic function with 2 methods)

julia> getfield(Main, Symbol(fn))()
1.448981716732318e9
您可以在此处将
Main
更改为任何模块,以仅查看该模块中的函数。这使您可以将可用函数集约束为仅该模块中可用的函数集。您可以使用“裸模块”创建一个名称空间,该名称空间只包含您填充它的函数,默认情况下,无需从
Base
导入所有名称

坏方法 有一种不同的方法不推荐,但许多人似乎首先想到的是为调用函数的代码构造一个字符串,然后解析该字符串并对其求值。例如:

julia> eval(parse("$fn()")) # NOT RECOMMENDED
1.464877410113412e9
fn = "deg2rad" # converts angles in degrees to radians

const f = getfield(Main, Symbol(fn))

function fast(n)
    t = 0.0
    for i = 1:n
        t += f(i)
    end
    return t
end

julia> @time fast(10^6) # once for JIT compilation
  0.010055 seconds (2.97 k allocations: 142.459 KB)
8.72665498661791e9

julia> @time fast(10^6) # now it's fast
  0.003055 seconds (6 allocations: 192 bytes)
8.72665498661791e9

julia> @time fast(10^6) # see?
  0.002952 seconds (6 allocations: 192 bytes)
8.72665498661791e9
虽然这是诱人的简单,但不推荐,因为它是缓慢的,脆弱的和危险的。解析和求值代码本质上要复杂得多,因此比在模块中进行名称查找要慢——名称查找本质上只是一个哈希表查找。在Julia中,代码只是及时编译而不是解释,eval的速度要慢得多,成本也更高,因为它不仅涉及解析,还涉及生成LLVM代码、运行优化过程、发出机器代码,然后最终调用函数。解析和求值字符串也很脆弱,因为当代码转换为文本时,所有预期的含义都会被丢弃。例如,假设有人意外地提供了一个空函数名,那么由于语法的意外相似性,这段代码旨在调用函数的事实完全消失了:

julia> fn = ""
""

julia> eval(parse("$fn()"))
()
哎呀。那根本不是我们想要的。在这种情况下,这种行为是相当无害的,但很容易变得更糟:

julia> fn = "println(\"rm -rf /important/directory\"); time"
"println(\"rm -rf /important/directory\"); time"

julia> eval(parse("$fn()"))
rm -rf /important/directory
1.448981974309033e9
如果用户的输入不可信,这将是一个巨大的安全漏洞。即使您信任用户,他们也有可能意外地提供输入,而这些输入会造成意想不到的不良后果。名称查找方法避免了以下问题:

julia> getfield(Main, Symbol(fn))()
ERROR: UndefVarError: println("rm -rf /important/directory"); time not defined
 in eval(::Module, ::Any) at ./boot.jl:225
 in macro expansion at ./REPL.jl:92 [inlined]
 in (::Base.REPL.##1#2{Base.REPL.REPLBackend})() at ./event.jl:46
查找名称然后将其作为函数调用的目的是明确的,而不是在生成的字符串语法中隐式的,因此在最坏的情况下,会出现一个关于未定义奇怪名称的错误

演出 如果要在内部循环中或作为某些递归计算的一部分调用动态指定的函数,则每次调用该函数时都要避免执行
getfield
查找。在这种情况下,您需要做的就是在定义调用它的迭代/递归过程之前,将
const
绑定到动态指定的函数。例如:

julia> eval(parse("$fn()")) # NOT RECOMMENDED
1.464877410113412e9
fn = "deg2rad" # converts angles in degrees to radians

const f = getfield(Main, Symbol(fn))

function fast(n)
    t = 0.0
    for i = 1:n
        t += f(i)
    end
    return t
end

julia> @time fast(10^6) # once for JIT compilation
  0.010055 seconds (2.97 k allocations: 142.459 KB)
8.72665498661791e9

julia> @time fast(10^6) # now it's fast
  0.003055 seconds (6 allocations: 192 bytes)
8.72665498661791e9

julia> @time fast(10^6) # see?
  0.002952 seconds (6 allocations: 192 bytes)
8.72665498661791e9
绑定
f
必须是常数才能获得最佳性能,因为否则编译器无法知道您不会在任何时候更改
f
以指向另一个函数(甚至不是函数的东西),因此,它必须在每次循环迭代中动态地发出看起来像
f
的代码——实际上就像在循环中手动调用
getfield
一样。这里,由于
f
const
,编译器知道
f
不能更改,因此它可以发出快速代码,直接调用正确的函数。但编译器有时甚至可以做得更好–在这种情况下,它实际上将
deg2rad
函数的实现内联,这只是pi/180的乘法:

julia> @code_llvm fast(100000)

define double @julia_fast_51089(i64) #0 {
top:
  %1 = icmp slt i64 %0, 1
  br i1 %1, label %L2, label %if.preheader

if.preheader:                                     ; preds = %top
  br label %if

L2.loopexit:                                      ; preds = %if
  br label %L2

L2:                                               ; preds = %L2.loopexit, %top
  %t.0.lcssa = phi double [ 0.000000e+00, %top ], [ %5, %L2.loopexit ]
  ret double %t.0.lcssa

if:                                               ; preds = %if.preheader, %if
  %t.04 = phi double [ %5, %if ], [ 0.000000e+00, %if.preheader ]
  %"#temp#.03" = phi i64 [ %2, %if ], [ 1, %if.preheader ]
  %2 = add i64 %"#temp#.03", 1
  %3 = sitofp i64 %"#temp#.03" to double
  %4 = fmul double %3, 0x3F91DF46A2529D39         ; deg2rad(x) = x*(pi/180)
  %5 = fadd double %t.04, %4
  %6 = icmp eq i64 %"#temp#.03", %0
  br i1 %6, label %L2.loopexit, label %if
}
如果需要对许多不同的动态指定函数执行此操作,并且正在使用Julia 0.5(每晚),则甚至可以将要调用的函数作为参数传递:

function fast(f,n)
    t = 0.0
    for i = 1:n
        t += f(i)
    end
    return t
end

julia> @time fast(getfield(Main, Symbol(fn)), 10^6)
  0.007483 seconds (1.70 k allocations: 76.670 KB)
8.72665498661791e9

julia> @time fast(getfield(Main, Symbol(fn)), 10^6)
  0.002908 seconds (6 allocations: 192 bytes)
8.72665498661791e9

这将生成与上面的单参数
fast
相同的快速代码,但将为您调用它的每个不同函数
f
生成一个新版本。

谢谢。我知道必须有更好的办法。作为朱莉娅的新来者,我发现这是一个超级答案!