【问题标题】:How to get argument names using reflection如何使用反射获取参数名称
【发布时间】:2010-10-11 23:04:20
【问题描述】:

我想在 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 扩展的哪种组合最容易工作。我不认为 caller+ParseTree 本身就足够了。

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

def add x, y 
  check_positive
  return x + y
end

如果xy 不是正数,check_positive 会抛出异常。显然,它的意义远不止于此,但希望这能提供足够的动力。

【问题讨论】:

  • 您似乎想确定执行顺序和参数。您是否有理由不能使用日志记录?您甚至可以进行一些元编程来打开类中的所有方法并自动记录参数以执行您想要的操作。

标签: ruby reflection


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

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

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

    格式是符号对的数组:类型(必需、可选、剩余、块)和名称。

    你想要的格式,试试

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

    当然,这仍然无法让您访问调用者。

    【讨论】:

    • 这也不会返回默认值。例如:def person(name: "calvin")method.parameters 会给[[:key, :name]]
    • 默认值是动态评估的,您无法获取有关它们的静态信息。 def foo(par = rand()); method(:foo).parameters 应该返回什么?
    • 这里的instance_method 是什么意思?
    • @fraxture 有点晚了,不过是A类实例的一个方法。相当于做:A.new.methods 而不是A.methods
    【解决方案3】:

    我建议你看看 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 中挑选最好的部分。

    【讨论】:

      【解决方案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 的东西更能够自动化,因为您不需要触及现有代码。实际上,这就是 debug.rb 的实现方式,就 set_trace_func 而言。

      正如您所概述的那样,您的确切问题的解决方案确实基本上是。使用调用者+分析树,或打开文件并以这种方式获取数据。我知道没有反射功能可以让您获得参数的名称。您可以通过获取关联的方法对象并调用 #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
      

      当然,这可以做得更复杂,这确实是我提到的库提供的一些内容,但也许这是一种让你到达你想去的地方的方法,而不必走你计划用来获取的路径那里?

      【讨论】:

      • 其实你描述的方法显然无法区分参数和局部变量,同时也无法自动工作
      • 嗯,这适用于按合同进行设计,但与只有一个可以调用的函数相比,它似乎有点笨拙。理想的情况是能够轻松添加和删除检查。
      【解决方案5】:

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

      那是因为您尝试执行的操作不受支持。这是可能的(在 ruby​​ 中一切皆有可能),但没有记录或已知的方法来做到这一点。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-05-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-03
        • 1970-01-01
        相关资源
        最近更新 更多