本文介绍了使用Ruby的TracePoint获取方法参数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我可以使用TracePoint API来访问Ruby方法的参数:

I'm able to get access to a Ruby method's arguments using the TracePoint API:

def foo(foo_arg)
end

trace = TracePoint.trace(:call, :c_call) do |tp|
  tp.disable
  case tp.method_id
  when :foo, :sub
    method = eval("method(:#{tp.method_id})", tp.binding)
    method.parameters.each do |p|
      puts "#{p.last}: #{tp.binding.local_variable_get(p.last)}"
    end
  end
  tp.enable
end

trace.enable

foo(10)
# => foo_arg: 10

但是,当我使用c方法调用尝试此操作时,会出现错误.

However when I try this with a c method call, I get an error.

"foo".sub(/(f)/) { $1.upcase }
script.rb:20:in `method': undefined method `sub' for class `Object' (NameError)
    from script.rb:20:in `<main>'
    from script.rb:8:in `eval'
    from script.rb:8:in `block in <main>'
    from script.rb:20:in `<main>'

这似乎是由于使用C方法调用和常规Ruby方法调用返回的绑定之间存在差异而发生的.

This looks like it happens because of a discrepancy between the binding returned when using a C method call and regular Ruby method call.

在Ruby中,tp.self等于tp.binding.eval("self")main,但是在C情况下,tp.self"foo",而tp.binding.eval("self")main.有没有办法将使用TracePoint的参数传递给Ruby和C定义的方法?

In the Ruby case tp.self is equal to tp.binding.eval("self") is main however in the C case tp.self is "foo" and tp.binding.eval("self") is main. Is there a way to get the arguments passed into a method using TracePoint for both Ruby and C defined methods?

推荐答案

在您指出问题并记录在红宝石文档tp.self返回一个跟踪的对象,该对象具有您要查找的method方法.我认为您应该使用

As you point in your question and as it documented in ruby documentation, tp.self returns a traced object, which have a method method you are looking for.I think you should use

method = tp.self.method(tp.method_id)

代替

method = eval("method(:#{tp.method_id})", tp.binding)

更新.有关您所讨论的最后一段的一些解释.在第一种情况下(调用foo时)tp.self指向main,因为您在主上下文中定义了foo方法,而在第二种情况下它又指向了String对象,因为在此定义了sub.但是tp.binding.eval("self")在两种情况下都会返回main,因为它会返回调用上下文(而不是您期望的定义"上下文),并且在两种情况下均为main.

UPDATE. Some explanation regarding your last paragraph in question. tp.self in first case (when you call foo) is point to main, because you define foo method in main context and it points to String object in second case because sub is defined there. But tp.binding.eval("self") returns main in both cases because it returns a calling context (not a 'define' context as you expect) and in both cases it is main.

更新(回复评论),我认为唯一的方法就是猴子补丁sub和您感兴趣的所有其他方法.代码示例:

UPDATE (in reply to comment) I think that the only way to do this is to monkey patch sub and all other methods that you are interesting for. Code example:

class String
  alias_method :old_sub, :sub
  def sub(*args, &block)
    old_sub(*args, &block)
  end
end

trace = TracePoint.trace(:call, :c_call) do |tp|
  tp.disable
  case tp.method_id
  when :sub
    method = tp.self.method(tp.method_id)
    puts method.parameters.inspect
  end
  tp.enable
end

trace.enable

"foo".sub(/(f)/) { |s| s.upcase }

一个很大的缺点是您不能在原始块中使用$1, $2, ... var.如所指出的,此处在哪里无法使其正常工作.但是,您仍然可以使用块参数(在我的示例中为s).

One big drawback is that you can't use $1, $2, ... vars in your original blocks. As pointed here where is no way to make it works. However you can still use block parameters (s in my example).

这篇关于使用Ruby的TracePoint获取方法参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-23 11:09