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

Ruby 如何使用反射获取参数名,ruby,reflection,Ruby,Reflection,我想用Ruby做一些相当重的反射。我想创建一个函数,它返回调用堆栈上层的各种调用函数的参数名(只需高一个就足够了,但为什么不在这里呢?)。我可以使用Kernel.caller,转到该文件并解析参数列表,但这样做既难看又不可靠 我想要的功能将以以下方式工作: module A def method1( tuti, fruity) foo end def method2(bim, bam, boom) foo end def foo print ca

我想用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,不知道如何使用它来完成这项任务。创建一个类似C的扩展是另一种可能性,但如果有人已经为我做了,那就太好了

我可以看出我可能需要某种C扩展。我想这意味着我的问题是什么样的C扩展组合最容易工作。我认为caller+ParseTree本身是不够的

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

def add x, y 
  check_positive
  return x + y
end

其中,如果
x
y
不为正,则
check\u positive
将引发异常。显然,还有更多的方法,但希望这能提供足够的动力。

我有一种方法非常昂贵,而且几乎不起作用

 $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
 5
 2
 checked_rb.rb:13:in `add': bad argument (RuntimeError)
         from checked_rb.rb:29
我很好奇为什么你会想要这样一种通用的功能

我很好奇,你认为这个功能会允许自动调试吗?您仍然需要向“foo”函数注入调用。事实上,基于
set\u trace\u func
的东西更能实现自动化,因为您不需要接触现有代码。实际上,就set_trace_func而言,debug.rb就是这样实现的

正如你所概述的,你的确切问题的解决方案基本上是正确的。使用caller+parsetree,或者打开文件并以这种方式获取数据。据我所知,没有反射功能可以让您获得参数的名称。您可以通过抓取关联的方法对象并调用
#arity
来批准我的解决方案,然后推断
局部变量
中哪些是参数,但尽管该函数的结果似乎是有序的,但我不确定依赖它是否安全。如果你不介意我问你,一旦你有了你描述的数据和接口,你打算用它做什么?当我想象这个功能的用途时,我最初并没有想到自动调试,尽管这可能是我想象的失败


啊哈

我会用不同的方式来处理这个问题。已经有几个ruby库用于按契约进行设计,包括ruby契约、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)
输出

 tuti, fruity
 bim, bam, boom, x, y
 5
 2
 checked_rb.rb:13:in `add': bad argument (RuntimeError)
         from checked_rb.rb:29
当然,这可以变得更加复杂,这确实是我提到的一些库所提供的,但也许这是一种方法,可以让你到达你想去的地方,而不必走你计划要去的路


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

那是因为你试图做的事情没有得到支持。这是可能的(ruby中的一切都是可能的),但是没有文档记录或已知的方法来实现这一点


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

我建议您看看Merb的action args库

require 'rubygems'
require 'merb'

include GetArgs

def foo(bar, zed=42)
end

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

如果你不想依赖Merb,你可以从Ruby 1.9.2中选择最好的部分。

在Ruby 1.9.2中,你可以通过
Proc
(当然也包括
方法或
取消绑定方法的
参数)获得任何
Proc
的参数列表:

A.instance_method(:method1).parameters # => [[:req, :tuti], [:req, :fruity]]
格式是成对符号的数组:类型(必需、可选、rest、块)和名称

要获得所需的格式,请尝试

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

当然,这仍然不能让您访问调用者。

如果您也想要默认值的值,那么还有“参数”gem


事实上,您描述的方法显然无法区分参数和局部变量,同时也无法自动工作,这适用于契约式设计,但与只调用一个函数相比,它似乎有点笨拙。理想情况是能够轻松地添加和删除检查。看起来您想要确定执行顺序和参数。有什么原因不能使用日志记录吗?您甚至可以执行一些元编程来打开类中的所有方法,并自动记录参数以执行所需操作。这也不会返回默认值。例如:
def person(姓名:“calvin”)
<代码>方法。参数
将给出
[[:key,:name]]
。默认值是动态计算的,您无法获取关于它们的静态信息。应该如何定义foo(par=rand());方法(:foo).parameters
返回?这里的
instance\u method
是什么意思?@fraxture有点晚,但是类
A
的实例的方法。相当于这样做:
A.new.methods
而不是
A.methods