代码之家  ›  专栏  ›  技术社区  ›  Joe Soul-bringer

如何使用反射获取参数名

  •  22
  • Joe Soul-bringer  · 技术社区  · 17 年前

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

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

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

    def add x, y 
      check_positive
      return x + y
    end
    

    check_positive 如果 x y 你的回答不是肯定的。显然,还有更多,但希望这能给我们足够的动力。

    5 回复  |  直到 9 年前
        1
  •  41
  •   Jörg W Mittag    16 年前

    在Ruby 1.9.2中,您可以轻松地获得任何 Proc (当然也包括任何 Method UnboundMethod )与 Proc#parameters

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

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

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

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

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

        2
  •  15
  •   Antti Tarvainen    17 年前

    我建议你看看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 .

        3
  •  4
  •   Logan Capaldo    17 年前

     $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
    

    我很好奇为什么你会想要这样一种通用的功能。

    set_trace_func 更能够自动执行,因为您不需要接触现有代码。实际上,就set_trace_func而言,debug.rb就是这样实现的。

    #arity 然后推断出 local_variables 都是参数,但尽管该函数的结果似乎是有序的,但我不确定依赖它是否安全。如果你不介意我问你,一旦你有了你描述的数据和接口,你打算用它做什么?当我想象这个功能的用途时,我最初并没有想到自动调试,尽管这可能是我想象的失败。


    啊哈!

    我会用不同的方式来处理这个问题。已经有几个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)
    

    输出

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

    当然,这可以变得更加复杂,这确实是我提到的一些库所提供的,但也许这是一种方法,可以让你到达你想去的地方,而不必走你计划要去的路?

        4
  •  1
  •   rogerdpack    16 年前

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

    $ gem install rdp-arguments
    $ irb
    >> require 'arguments'
    >> require 'test.rb' # class A is defined here
    >> Arguments.names(A, :go)
    
        5
  •  -3
  •   Orion Edwards    17 年前

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

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

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