使用 Ruby 的 TracePoint 获取方法参数
Get Method Arguments using Ruby's 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
然而,当我尝试使用 c 方法调用时,出现错误。
"foo".sub(/(f)/) { .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 方法调用时返回的绑定之间存在差异。
在 Ruby 的情况下 tp.self
等于 tp.binding.eval("self")
是 main
但是在 C 的情况下 tp.self
是 "foo"
和 tp.binding.eval("self")
是 main
。对于 Ruby 和 C 定义的方法,有没有办法使用 TracePoint 将参数传递到方法中?
正如您在问题中指出的那样以及在 ruby documentation 中记录的那样,tp.self
returns 一个跟踪对象,它具有您正在寻找的 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")
returns main
在这两种情况下,因为它 returns 一个调用上下文(不是你期望的 '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 }
一个很大的缺点是您不能在原始块中使用 , , ...
变量。正如 here 所指出的那样,没有办法让它发挥作用。但是,您仍然可以使用块参数(在我的示例中为 s
)。
我可以使用 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
然而,当我尝试使用 c 方法调用时,出现错误。
"foo".sub(/(f)/) { .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 方法调用时返回的绑定之间存在差异。
在 Ruby 的情况下 tp.self
等于 tp.binding.eval("self")
是 main
但是在 C 的情况下 tp.self
是 "foo"
和 tp.binding.eval("self")
是 main
。对于 Ruby 和 C 定义的方法,有没有办法使用 TracePoint 将参数传递到方法中?
正如您在问题中指出的那样以及在 ruby documentation 中记录的那样,tp.self
returns 一个跟踪对象,它具有您正在寻找的 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")
returns main
在这两种情况下,因为它 returns 一个调用上下文(不是你期望的 '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 }
一个很大的缺点是您不能在原始块中使用 , , ...
变量。正如 here 所指出的那样,没有办法让它发挥作用。但是,您仍然可以使用块参数(在我的示例中为 s
)。