如何使用反射获取参数名称

时间:2009-03-07 19:10:37

标签: ruby reflection

我想在Ruby中做一些相当重的反思。我想创建一个函数,它返回调用堆栈上方各种调用函数的参数名称(只有一个更高就足够了,但为什么要停在那里?)。我可以使用Kernel.caller,转到文件并解析参数列表,但这将是丑陋和不可靠的。

我想要的功能可以通过以下方式工作:

module A
  def method1( tuti, fruity) 
    foo
  end
  def method2(bim, bam, boom)  
    foo
  end
  def foo
    print caller_args[1].join(",") #the "1" mean one step up the call stack
  end

end

A.method1
 #prints "tuti,fruity"
A.method2
 #prints "bim, bam, boom"

我不介意使用ParseTree或类似的工具来完成这项任务,但是看看Parsetree,如何将它用于此目的并不明显。创建一个像this这样的C扩展是另一种可能性,但如果有人已经为我做过这样的话会很好。

我可以看到我可能需要某种C扩展。我想这意味着我的问题是C扩展的哪种组合最容易起作用。我不认为来电者+ ParseTree本身就足够了。

至于为什么我想这样做,而不是说“自动调试”,或许我应该说我想使用这个功能来自动检查函数的调用和返回条件:

def add x, y 
  check_positive
  return x + y
end

如果check_positivex不是正数,y会引发异常。显然,会有更多的东西,但希望这给了足够的动力。

5 个答案:

答案 0 :(得分:39)

在Ruby 1.9.2中,您可以通过Proc轻松获取任何Method(当然也包括任何UnboundMethodProc#parameters)的参数列表:

A.instance_method(:method1).parameters # => [[:req, :tuti], [:req, :fruity]]

格式是符号对的数组:类型(必填,可选,休息,阻止)和名称。

对于您想要的格式,请尝试

A.instance_method(:method1).parameters.map(&:last).map(&:to_s)
  # => ['tuti', 'fruity']

当然,这仍然无法让您访问来电者。

答案 1 :(得分:15)

我建议你看看Merb的action-args库。

require 'rubygems'
require 'merb'

include GetArgs

def foo(bar, zed=42)
end

method(:foo).get_args # => [[[:bar], [:zed, 42]], [:zed]]

如果您不想依赖Merb,可以从the source code in github中选择并选择最佳部分。

答案 2 :(得分:4)

我有一种非常昂贵且几乎可以工作的方法。

 $shadow_stack = []

 set_trace_func( lambda {
   |event, file, line, id, binding, classname|
   if event == "call"
     $shadow_stack.push( eval("local_variables", binding) )
   elsif event == "return"
     $shadow_stack.pop
   end
 } )

 def method1( tuti, fruity )
   foo
 end
 def method2(bim, bam, boom)
   foo
   x = 10
   y = 3
 end

 def foo
   puts $shadow_stack[-2].join(", ")
 end

 method1(1,2)
 method2(3,4,4)

输出

 tuti, fruity
 bim, bam, boom, x, y

我很好奇为什么你会以这样一种普遍的方式想要这样的功能。

我很好奇你认为这个功能如何允许自动调试?你仍然需要注入你的“foo”函数。事实上,基于set_trace_func的内容更能自动化,因为您不需要触及现有代码。实际上,就set_trace_func而言,这就是debug.rb的实现方式。

正如您所概述的那样,您的确切问题的解决方案确实基本上是正确的。使用caller + parsetree,或打开文件并以这种方式获取数据。我所知道的没有反射功能可以让你获得参数的名称。您可以通过抓取关联的方法对象并调用#arity来批准我的解决方案然后推断出local_variables的参数是什么,但是虽然看起来该函数的结果是有序的,但我不确定它依靠这个是安全的。如果你不介意我问,一旦你掌握了你描述的数据和界面,你打算用它做什么?当我想象使用这个功能时,自动调试并不是最初想到的,尽管我可能没有想象力。


啊哈!

我会以不同的方式接近这一点。有几个ruby库已经按合同进行设计,包括ruby-contract,rdbc等。

另一个选择是编写如下内容:

 def positive
   lambda { |x| x >= 0 }
 end

 def any
   lambda { |x| true }
 end

 class Module
   def define_checked_method(name, *checkers, &body)
     define_method(name) do |*args|
       unless checkers.zip(args).all? { |check, arg| check[arg] }
         raise "bad argument"
       end
       body.call(*args)
     end
   end
 end

 class A
   define_checked_method(:add, positive, any) do |x, y|
     x + y
   end
 end

 a = A.new
 p a.add(3, 2)
 p a.add(3, -1)
 p a.add(-4, 2)

输出

 5
 2
 checked_rb.rb:13:in `add': bad argument (RuntimeError)
         from checked_rb.rb:29

当然,这可以变得更加复杂,实际上这就是我提到的库提供的一些内容,但也许这是一种让你到达目的地的方式而不必走你计划用来获取的路径有?

答案 3 :(得分:1)

如果你想要默认值的值,那么就是“arguments”gem

$ gem install rdp-arguments
$ irb
>> require 'arguments'
>> require 'test.rb' # class A is defined here
>> Arguments.names(A, :go)

答案 4 :(得分:-3)

  

事实上,您描述的方法显然无法区分参数与局部变量,同时也无法自动工作

那是因为你想要做的不是支持的东西。它是可能的(红宝石中的一切都是可能的),但没有记录或已知的方法来做它。

你可以像logan建议的那样评估回溯,或者你可以破坏你的C编译器并破解ruby的源代码。我有理由相信没有其他方法可以做到这一点。