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

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

问题描述

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
}

如果您需要使用许多不同的动态指定函数来执行此操作,那么您甚至可以将要调用的函数作为参数传递:

If you need to do this with many different dynamically specified functions, 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.

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

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