在Ruby中调用dynamic方法

据我所知,有三种方法可以在Ruby中dynamic调用方法:

方法1:

s = SomeObject.new method = s.method(:dynamic_method) method.call 

方法2:

 s = SomeObject.new s.send(:dynamic_method) 

方法3:

 s = SomeObject.new eval "s.dynamic_method" 

通过对它们进行基准testing,我确定方法1是最快的,方法2更慢,方法3是最慢的。

我也发现.send.send都允许调用私有方法,而eval不。

所以我的问题是:是否有任何理由使用.sendeval ? 为什么你不总是只使用最快的方法? 这些调用dynamic方法的方法还有其他什么区别?

有什么理由可以使用send吗?

call需要一个方法对象, send不:

 class Foo def method_missing(name) "#{name} called" end end Foo.new.send(:bar) #=> "bar called" Foo.new.method(:bar).call #=> undefined method `bar' for class `Foo' (NameError) 

有没有任何理由使用eval

eval评估任意expression式,这不仅仅是调用一个方法。


关于基准, send似乎比method + call更快:

 require 'benchmark' class Foo def bar; end end Benchmark.bm(4) do |b| b.report("send") { 1_000_000.times { Foo.new.send(:bar) } } b.report("call") { 1_000_000.times { Foo.new.method(:bar).call } } end 

结果:

  user system total real send 0.210000 0.000000 0.210000 ( 0.215181) call 0.740000 0.000000 0.740000 ( 0.739262) 

这样想:

方法1(method.call):单个运行时

如果您直接在程序上运行Ruby一次,则可以控制整个系统,并且可以通过“method.call”方法保持“指向您的方法的指针”。 所有你正在做的是持有一个“实时代码”,你可以随时运行的句柄。 这基本上和直接从对象内部调用方法一样快(但不像使用object.send那么快 – 请参阅其他答案中的基准)。

方法2(object.send):将方法的名称保存到数据库

但是如果要将要调用的方法的名称存储在数据库中,并且在将来的应用程序中想要通过在数据库中查找来调用该方法名称呢? 然后你会使用第二种方法,这会导致ruby使用第二个“s.send(:dynamic_method)”方法调用任意方法名称。

方法3(eval):自修改方法代码

如果你想以一种将全新代码运行的方式来编写/修改/保存代码到数据库呢? 您可能会定期修改写入数据库的代码,并希望每次都以新代码运行。 在这种情况下(很不寻常的情况),你会想要使用第三种方法,它可以把你的方法代码写成一个string,在稍后的date将它加载回来,然后把它全部运行起来。

对于什么是值得的,一般认为Ruby世界中使用Eval(方法3)是一个糟糕的forms,除非是非常非常深奥和罕见的情况。 所以你应该坚持方法1和2几乎所有遇到的问题。

我从@Stefan更新了基准,以检查在保存对方法的引用时是否有一些速度改进。 但是,再次sendcall要快得多

 require 'benchmark' class Foo def bar; end end foo = Foo.new foo_bar = foo.method(:bar) Benchmark.bm(4) do |b| b.report("send") { 1_000_000.times { foo.send(:bar) } } b.report("call") { 1_000_000.times { foo_bar.call } } end 

这是结果:

  user system total real send 0.080000 0.000000 0.080000 ( 0.088685) call 0.110000 0.000000 0.110000 ( 0.108249) 

所以send似乎是一个要采取的。

这里是所有可能的方法调用:

 require 'benchmark/ips' class FooBar def name; end end el = FooBar.new Benchmark.ips do |x| x.report('plain') { el.name } x.report('eval') { eval('el.name') } x.report('method call') { el.method(:name).call } x.report('send sym') { el.send(:name) } x.report('send str') { el.send('name') } x.compare! end 

结果是:

 Warming up -------------------------------------- plain 236.448ki/100ms eval 20.743ki/100ms method call 131.408ki/100ms send sym 205.491ki/100ms send str 168.137ki/100ms Calculating ------------------------------------- plain 9.150M (± 6.5%) i/s - 45.634M in 5.009566s eval 232.303k (± 5.4%) i/s - 1.162M in 5.015430s method call 2.602M (± 4.5%) i/s - 13.009M in 5.010535s send sym 6.729M (± 8.6%) i/s - 33.495M in 5.016481s send str 4.027M (± 5.7%) i/s - 20.176M in 5.027409s Comparison: plain: 9149514.0 i/s send sym: 6729490.1 i/s - 1.36x slower send str: 4026672.4 i/s - 2.27x slower method call: 2601777.5 i/s - 3.52x slower eval: 232302.6 i/s - 39.39x slower 

预计普通呼叫是最快的,没有任何额外的分配,符号查找,只是查找和评估方法。

至于通过符号send ,它比通过string更快,因为它更容易为符号分配内存。 一旦被定义,它就会长期存储在内存中,而且不会重新分配。

(1)需要为Proc对象分配内存(2)我们正在调用类中的方法,导致额外的方法查找,这也需要花费时间。

eval是运行解释器所以它是最重的。

sendeval的重点在于您可以dynamic更改命令。 如果您想要执行的方法是固定的,那么您可以在不使用sendeval情况下硬连线该方法。

 receiver.fixed_method(argument) 

但是当你想要调用一个不同的或者你不知道的方法时,你就不能直接写出来。 因此使用send或者eval

 receiver.send(method_that_changes_dynamically, argument) eval "#{code_to_evaluate_that_changes_more_dramatically}" 

send另外一个用处是,正如你注意到的那样,你可以用send来调用一个明确的接收方法。