Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/ruby/21.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ruby中的动态方法调用_Ruby_Metaprogramming_Dynamic Method - Fatal编程技术网

Ruby中的动态方法调用

Ruby中的动态方法调用,ruby,metaprogramming,dynamic-method,Ruby,Metaprogramming,Dynamic Method,据我所知,在Ruby中动态调用方法有三种方法: 方法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" 通过对它们进行基准测试,我确定方法1是目前为止最快的,方法2较慢,方法3是目前为止最慢的 我还发现.call和.

据我所知,在Ruby中动态调用方法有三种方法:

方法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"
通过对它们进行基准测试,我确定方法1是目前为止最快的,方法2较慢,方法3是目前为止最慢的

我还发现
.call
.send
都允许调用私有方法,而
eval
则不允许

所以我的问题是:是否有任何理由使用
.send
eval
?你为什么不总是使用最快的方法呢?这些调用动态方法的方法还有什么区别

是否有任何理由使用
send

需要方法对象,但不:

是否有任何理由使用
eval

计算任意表达式,它不仅仅用于调用方法


关于基准测试,
发送
似乎比
方法
+
调用
更快:

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)

send
eval
的全部要点是您可以动态更改命令。如果要执行的方法是固定的,则可以在不使用
send
eval
的情况下硬连接该方法

receiver.fixed_method(argument)
receiver.send(method_that_changes_dynamically, argument)
eval "#{code_to_evaluate_that_changes_more_dramatically}"
但是,当您想要调用一个变化的方法或您事先不知道的方法时,您不能直接编写它。因此使用了
send
eval

receiver.fixed_method(argument)
receiver.send(method_that_changes_dynamically, argument)
eval "#{code_to_evaluate_that_changes_more_dramatically}"
send
的另一个用途是,正如您所注意到的,您可以使用
send
调用带有显式接收器的方法,您可以这样想:

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

方法2(object.send):将方法的名称持久化到数据库 但是,如果要在数据库中存储要调用的方法的名称,并且在将来的应用程序中要通过在数据库中查找来调用该方法的名称,该怎么办?然后使用第二种方法,这会导致ruby使用第二种“s.send(:dynamic_method)”方法调用任意方法名

方法3(eval):自修改方法代码 如果您希望以将该方法作为全新代码运行的方式向数据库写入/修改/持久化代码,该怎么办?您可能会定期修改写入数据库的代码,并希望每次都以新代码的形式运行。在这种情况下(非常罕见的情况),您可能希望使用第三种方法,它允许您将方法代码作为字符串写出,在稍后的某个日期重新加载,然后完整地运行它


无论如何,在Ruby世界中,使用Eval(方法3)通常被视为不好的形式,除非是在非常、非常深奥和罕见的情况下。因此,对于几乎所有遇到的问题,您都应该坚持使用方法1和方法2。

我从@Stefan更新了基准测试,以检查在保存对方法的引用时是否有一些速度改进。但是再次强调–
发送
呼叫

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.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
可以预期,普通调用是最快的,不需要任何额外的分配、符号查找,只需查找和计算方法

至于
send
via symbol,它比via string更快,因为它更容易为symbol分配内存。一旦它被定义,它将长期存储在内存中,并且没有重新分配

关于
方法(:name)
(1)需要为
Proc
对象分配内存(2)我们在类中调用该方法,这会导致额外的方法查找,这也需要时间


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

好的建设性问题:)+1..@Abraham+1好问题!您可能希望在方法2代码示例中包含基准测试结果并修复键入错误。“我还发现.call和.send都允许调用私有方法,而eval则不允许。”您可以使用
.public\u send
而不是
。send
来禁止调用私有方法。感谢您深思熟虑且信息丰富的回答,我投了更高的票。我在下面给Stefan一个公认的答案,包括一组基准测试,提出方法_missing,并首先响应。谢谢你!这是一个更准确的基准。谢谢你的发帖。