使用Ruby的TracePoint获取方法参数
我可以使用TracePoint API访问Ruby方法的参数:
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
但是,当我尝试使用ac方法调用时,我收到一个错误。
"foo".sub(/(f)/) { $1.upcase } script.rb:20:in `method': undefined method `sub' for class `Object' (NameError) from script.rb:20:in `' from script.rb:8:in `eval' from script.rb:8:in `block in ' from script.rb:20:in `'
这似乎是因为使用C方法调用时返回的绑定与常规Ruby方法调用之间的差异。
在Ruby的情况下, tp.self
等于tp.binding.eval("self")
是main
但是在C情况下tp.self
是"foo"
而tp.binding.eval("self")
是main
。 有没有办法将参数传递给使用TracePoint的方法用于Ruby和C定义的方法?
当你指出你的问题并且在ruby文档中记录时 , tp.self
返回一个跟踪对象,它有一个你正在寻找的method
方法。 我想你应该用
method = tp.self.method(tp.method_id)
代替
method = eval("method(:#{tp.method_id})", tp.binding)
更新 。 关于你最后一段的一些解释。 tp.self
在第一种情况下(当你调用foo
)是指向main
,因为你在主上下文中定义了foo
方法,并且它在第二种情况下指向String
对象,因为sub
在那里被定义。 但是tp.binding.eval("self")
在两种情况下都返回main
,因为它返回一个调用上下文(不是你期望的’define’上下文),并且在两种情况下它都是main
。
更新(回复评论)我认为这样做的唯一方法是修补补丁sub
和你感兴趣的所有其他方法。 代码示例:
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, ...
vars。 正如这里指出的那样,没有办法让它起作用。 但是,您仍然可以使用块参数(在我的示例中为s
)。