Ruby 中的动态方法调用
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/17454992/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Dynamic method calling in Ruby
提问by Abraham P
As far as I am aware there are three ways to dynamically call a method in Ruby:
据我所知,在 Ruby 中有三种动态调用方法的方法:
Method 1:
方法一:
s = SomeObject.new
method = s.method(:dynamic_method)
method.call
Method 2:
方法二:
s = SomeObject.new
s.send(:dynamic_method)
Method 3:
方法三:
s = SomeObject.new
eval "s.dynamic_method"
By benchmarking them I have established that Method 1 is by far the fastest, Method 2 is slower, and Method 3 is by far the slowest.
通过对它们进行基准测试,我确定方法 1 是迄今为止最快的,方法 2 较慢,方法 3 是迄今为止最慢的。
I have also found that .calland .sendboth allow calling private methods, while evaldoes not.
我还发现,.call和.send都允许调用私有方法,而eval不会。
So my question is: is there any reason to ever use .sendor eval? Why would you not always just use the fastest method? What other differences do these methods of calling dynamic methods have?
所以我的问题是:是否有任何理由使用.send或eval?为什么你不总是只使用最快的方法?这些调用动态方法的方法还有哪些其他区别?
采纳答案by Stefan
is there any reason to ever use
send?
有什么理由使用
send吗?
callneeds a method object, senddoesn't:
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)
is there any reason to ever use
eval?
有什么理由使用
eval吗?
evalevaluates arbitrary expressions, it's not just for calling a method.
eval评估任意表达式,它不仅用于调用方法。
Regarding benchmarks, sendseems to be faster than method+ call:
关于基准,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
Result:
结果:
user system total real
send 0.210000 0.000000 0.210000 ( 0.215181)
call 0.740000 0.000000 0.740000 ( 0.739262)
回答by Steve Midgley
Think of it this way:
可以这样想:
Method 1 (method.call): Single run-time
方法一(method.call):单次运行
If you run Ruby once on your program straight through, you control the entire system and you can hold onto a "pointer to your method" via the "method.call" approach. All you are doing is holding on to a handle to "live code" that you can run whenever you want. This is basically as fast as calling the method directly from within the object (but it is not as fast as using object.send - see benchmarks in other answers).
如果您直接在程序上运行 Ruby 一次,您就可以控制整个系统,并且可以通过“method.call”方法保持“指向您的方法的指针”。您所做的就是抓住“实时代码”的句柄,您可以随时运行该句柄。这基本上与直接从对象内部调用方法一样快(但它不如使用 object.send 快 - 请参阅其他答案中的基准测试)。
Method 2 (object.send): Persist name of method to database
方法二(object.send):将方法名持久化到数据库
But what if you want to store the name of the method you want to call in a database and in a future application you want to call that method name by looking it up in the database? Then you would use the second approach, which causes ruby to call an arbitrary method name using your second "s.send(:dynamic_method)" approach.
但是,如果您想将要调用的方法的名称存储在数据库中,并且在将来的应用程序中您想通过在数据库中查找来调用该方法的名称,该怎么办?然后您将使用第二种方法,这会导致 ruby 使用您的第二种“s.send(:dynamic_method)”方法调用任意方法名称。
Method 3 (eval): Self-modifying method code
方法三(eval):自修改方法代码
What if you want to write/modify/persist code to a database in a way that will run the method as brand new code? You might periodically modify the code written to the database and want it to run as new code each time. In this (very unusual case) you would want to use your third approach, which allows you to write your method code out as a string, load it back in at some later date, and run it in its entirety.
如果您想以一种将方法作为全新代码运行的方式将代码写入/修改/保留到数据库中,该怎么办?您可能会定期修改写入数据库的代码,并希望它每次都作为新代码运行。在这种情况下(非常不寻常的情况),您可能希望使用第三种方法,该方法允许您将方法代码写为字符串,稍后将其重新加载,然后完整地运行它。
For what it's worth, generally it is regarded in the Ruby world as bad form to use Eval (method 3) except in very, very esoteric and rare cases. So you should really stick with methods 1 and 2 for almost all problems you encounter.
就其价值而言,通常在 Ruby 世界中使用 Eval(方法 3)被认为是不好的形式,除非在非常非常深奥和罕见的情况下。因此,对于遇到的几乎所有问题,您都应该坚持使用方法 1 和方法 2。
回答by mpospelov
Here is all possible method calls:
这是所有可能的方法调用:
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
And results are:
结果是:
Warming up --------------------------------------
plain 236.448k i/100ms
eval 20.743k i/100ms
method call 131.408k i/100ms
send sym 205.491k i/100ms
send str 168.137k i/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
It's expected that plain call is the fastest, no any additional allocations, symbol lookups, just lookup and evaluation of method.
预计普通调用是最快的,没有任何额外的分配、符号查找,只是方法的查找和评估。
As for sendvia symbol, it is faster than via string as its much more easer to allocate memory for symbol. Once it's defined it's stored for long term in memory and there no reallocations.
至于send通过符号,它比通过字符串更快,因为它更容易为符号分配内存。一旦它被定义,它就会长期存储在内存中,并且没有重新分配。
The same reason can be said about method(:name)(1) it's requires to allocate memory for Procobject (2) we are calling the method in class which leads for additional method lookup which takes time too.
关于method(:name)(1) 需要为Proc对象分配内存(2) 我们正在调用类中的方法,这会导致额外的方法查找,这也需要时间。
evalis runs interpreter so it's the heaviest.
eval是运行解释器所以它是最重的。
回答by Tom Freudenberg
I updated the benchmark from @Stefan to check if there are some speed improvements when saving reference to method. But again – sendis much faster than call
我更新了@Stefan 的基准测试,以检查在保存对方法的引用时是否有一些速度改进。但同样 -send比call
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
These are the results:
这些是结果:
user system total real
send 0.080000 0.000000 0.080000 ( 0.088685)
call 0.110000 0.000000 0.110000 ( 0.108249)
So sendseems to be the one to take.
所以send似乎是要拿的。
回答by sawa
The whole point of sendand evalis that you can change the command dynamically. If the method you want to execute is fixed, then you can hard-wire that method without using sendor eval.
整点send和eval是可以动态改变的命令。如果您要执行的方法是固定的,那么您可以在不使用send或 的情况下硬连线该方法eval。
receiver.fixed_method(argument)
But when you want to invoke a method that varies or you do not know in advance, then you cannot write that directly. Hence the use of sendor eval.
但是当你想调用一个变化的方法或者你事先不知道时,你就不能直接写出来。因此使用sendor eval。
receiver.send(method_that_changes_dynamically, argument)
eval "#{code_to_evaluate_that_changes_more_dramatically}"
Additional use of sendis that, as you noticed, you can call a method with explicit receiver using send.
的其他用途send是,正如您所注意到的,您可以使用显式接收器调用方法send。

![ruby 使用哈希时获取与 [] 的比较?](/res/img/loading.gif)