朱莉娅:通过给定的字符串调用函数 [英] Julia: invoke a function by a given string

查看:60
本文介绍了朱莉娅:通过给定的字符串调用函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Does Julia support the reflection just like java?

我需要的是这样的东西:

What I need is something like this:

str = ARGS[1] # str is a string
# invoke the function str()

推荐答案

好方法

推荐的方法是将函数名称转换为符号,然后在适当的名称空间中查找该符号:

The Good Way

The recommended way to do this is to convert the function name to a symbol and then look up that symbol in the appropriate namespace:

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中的所有名称.

You can change Main here to any module to only look at functions in that module. This lets you constrain the set of functions available to only those available in that module. You can use a "bare module" to create a namespace that has only the functions you populate it with, without importing all name from Base by default.

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

A different approach that is not recommended but which many people seem to reach for first is to construct a string for code that calls the function and then parse that string and evaluate it. For example:

julia> eval(parse("$fn()")) # NOT RECOMMENDED
1.464877410113412e9

虽然这很简单,但不建议这样做,因为它速度慢,脆弱且危险.与在模块中进行名称查找相比,解析和逃避代码本质上要复杂得多,因此要慢-名称查找本质上只是哈希表查找.在朱莉娅(Julia)中,代码是即时编译而不是解释性的,因为eval不仅涉及解析,而且还生成LLVM代码,运行优化过程,发出代码,因此 更加缓慢且昂贵.机器代码,然后最后调用一个函数.解析和回避字符串也很脆弱,因为当代码转换为文本时,所有预期的含义都将被丢弃.例如,假设某人不小心提供了一个空的函数名称–然后,由于意外的语法相似性,该代码原本打算调用一个函数的事实就完全消失了:

While this is temptingly simple, it's not recommended since it is slow, brittle and dangerous. Parsing and evaling code is inherently much more complicated and thus slower than doing a name lookup in a module – name lookup is essentially just a hash table lookup. In Julia, where code is just-in-time compiled rather than interpreted, eval is much slower and more expensive since it doesn't just involve parsing, but also generating LLVM code, running optimization passes, emitting machine code, and then finally calling a function. Parsing and evaling a string is also brittle since all intended meaning is discarded when code is turned into text. Suppose, for example, someone accidentally provides an empty function name – then the fact that this code is intended to call a function is completely lost by accidental similarity of syntaxes:

julia> fn = ""
""

julia> eval(parse("$fn()"))
()

糟糕.那根本不是我们想要的.在这种情况下,该行为是相当无害的,但很容易变得更糟:

Oops. That's not what we wanted at all. In this case the behavior is fairly harmless but it could easily be much worse:

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

julia> eval(parse("$fn()"))
rm -rf /important/directory
1.448981974309033e9

如果用户的输入不受信任,则这是一个巨大的安全漏洞.即使您信任用户,他们仍然有可能意外地提供输入,这将导致意外和不良的后果.名称查找方法避免了以下问题:

If the user's input is untrusted, this is a massive security hole. Even if you trust the user, it is still possible for them to accidentally provide input that will do something unexpected and bad. The name lookup approach avoids these issues:

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

查找名称然后将其作为函数调用的意图是显式的,而不是在生成的字符串语法中是隐式的,因此最糟糕的是,会出现关于未定义陌生名称的错误.

The intent of looking up a name and then calling it as a function is explicit, instead of implicit in the generated string syntax, so at worst one gets an error about a strange name being undefined.

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

If you're going to call a dynamically specified function in an inner loop or as part of some recursive computation, you will want to avoid doing a getfield lookup every time you call the function. In this case all you need to do is make a const binding to the dynamically specified function before defining the iterative/recursive procedure that calls it. For example:

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一样有效.在这里,由于fconst,因此编译器知道f不能更改,因此它可以发出直接调用正确函数的快速代码.但是编译器有时甚至可以做得更好-在这种情况下,它实际上内嵌了deg2rad函数的实现,该函数只是与pi/180相乘的:

The binding f must be constant for optimal performance, since otherwise the compiler can't know that you won't change f to point at another function at any time (or even something that's not a function), so it has to emit code that looks f up dynamically on every loop iteration – effectively the same thing as if you manually call getfield in the loop. Here, since f is const, the compiler knows f can't change so it can emit fast code that just calls the right function directly. But the compiler can sometimes do even better than that – in this case it actually inlines the implementation of the deg2rad function, which is just a multiplication by 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(每晚),那么您甚至可以将要调用的函数作为参数传递:

If you need to do this with many different dynamically specified functions and you're using Julia 0.5 (nightly), then you can even pass the function to be called in as an argument:

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生成一个新版本.

This generates the same fast code as single-argument fast above, but will generate a new version for every different function f that you call it with.

这篇关于朱莉娅:通过给定的字符串调用函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆