在Ruby中,如何编写一个方法来显示任何对象的实例变量名称及其值

nop*_*ole 2 ruby debugging ruby-on-rails instance-variables

给定Ruby中的任何对象(在Rails上),我如何编写一个方法,以便它显示该对象的实例变量名称及其值,如下所示:

@x: 1
@y: 2
@link_to_point: #<Point:0x10031b298 @y=20, @x=38>
Run Code Online (Sandbox Code Playgroud)

(更新: inspect除了大对象之外,很难从200行输出中分解变量,比如在Rails中,当你request.inspectself.inspect在ActionView对象中时)

我还希望能够打印<br>到每个实例变量值的末尾,以便在网页上很好地打印出来.

现在的困难似乎是并非每个实例变量都有一个访问器,因此无法使用obj.send(var_name)调用它

(var_name删除了"@",因此"@x"变为"x")

更新:我想使用递归,它可以打印出更高级的版本:

#<Point:0x10031b462>
    @x: 1
    @y: 2
    @link_to_point: #<Point:0x10031b298>
        @x=38
        @y=20
Run Code Online (Sandbox Code Playgroud)

Ant*_*sky 6

我可能会这样写:

class Object
  def all_variables(root=true)
    vars = {}
    self.instance_variables.each do |var|
      ivar = self.instance_variable_get(var)
      vars[var] = [ivar, ivar.all_variables(false)]
    end
    root ? [self, vars] : vars
  end
end

def string_variables(vars, lb="\n", indent="\t", current_indent="")
  out             = "#{vars[0].inspect}#{lb}"
  current_indent += indent
  out            += vars[1].map do |var, ivar|
                      ivstr = string_variables(ivar, lb, indent, current_indent)
                      "#{current_indent}#{var}: #{ivstr}"
                    end.join
  return out
end

def inspect_variables(obj, lb="\n", indent="\t", current_indent="")
  string_variables(obj.all_variables, lb, indent, current_indent)
end
Run Code Online (Sandbox Code Playgroud)

Object#all_variables方法生成一个包含(0)给定对象的数组和(1)哈希映射实例变量名称到包含(0)实例变量和(1)哈希映射...的数组.因此,它为您提供了一个很好的递归结构.该string_variables函数很好地打印出该哈希值; inspect_variables只是一个方便的包装.因此,print inspect_variables(foo)为您提供换行符分隔选项,并print inspect_variables(foo, "<br />\n")为您提供包含HTML换行符的版本.如果要指定缩进,也可以这样做:print inspect_variables(foo, "\n", "|---")生成(无用的)伪树格式而不是基于制表符的缩进.

应该有一种合理的方法来编写一个each_variable函数,您可以向其提供回调(不必分配中间存储); 如果我想到的话,我会编辑这个答案以包含它. 编辑1:我想到了什么.

这是另一种写它的方式,我觉得它更好一些:

class Object
  def each_variable(name=nil, depth=0, parent=nil, &block)
    yield name, self, depth, parent
    self.instance_variables.each do |var|
      self.instance_variable_get(var).each_variable(var, depth+1, self, &block)
    end
  end
end

def inspect_variables(obj, nl="\n", indent="\t", sep=': ')
  out = ''
  obj.each_variable do |name, var, depth, _parent|
    out += [indent*depth, name, name ? sep : '', var.inspect, nl].join
  end
  return out
end
Run Code Online (Sandbox Code Playgroud)

Object#each_variable方法采用了许多可选参数,这些参数不是由用户指定的; 相反,它们被递归用于维持状态.给定的块传递(a)实例变量的名称,或者nil如果变量是递归的根; (b)变数; (c)递归下降的深度; (d),当前变量的父级,或者nil如果所述变量是递归的根.递归是深度优先的.该inspect_variables函数使用它来构建字符串.的obj参数是要遍历所述对象; nl是行分隔符; indent是在每个级别应用的缩进; 并sep分隔名称和值.

编辑2:这并没有真正为你的问题的答案添加任何东西,但是:只是为了证明我们在重新实现中没有丢失任何东西,这里是重新实现all_variableseach_variables.

def all_variables(obj)
  cur_depth = 0
  root      = [obj, {}]

  tree      = root
  parents   = []
  prev      = root

  obj.each_variable do |name, var, depth, _parent|
    next unless name

    case depth <=> cur_depth
      when -1 # We've gone back up
        tree = parents.pop(cur_depth - depth)[0]
      when +1 # We've gone down
        parents << tree
        tree = prev
      else # We're at the same level
        # Do nothing
    end

    cur_depth            = depth
    prev = tree[1][name] = [var, {}]
  end

  return root
end
Run Code Online (Sandbox Code Playgroud)

我觉得它应该更短,但这可能是不可能的; 因为我们现在没有递归,所以我们必须明确地维护堆栈(in parents).但这是可能的,所以这种each_variable方法同样适用(我觉得它更好一些).


nop*_*ole 5

我明白了... Antal必须在这里给出高级版本......

短版本可能是:

def p_each(obj)
  obj.instance_variables.each do |v|
    puts "#{v}: #{obj.instance_variable_get(v)}\n"
  end
  nil
end
Run Code Online (Sandbox Code Playgroud)

或者将其作为字符串返回:

def sp_each(obj)
  s = ""
  obj.instance_variables.each do |v|
    s += "#{v}: #{obj.instance_variable_get(v)}\n"
  end
  s
end
Run Code Online (Sandbox Code Playgroud)

或更短:

def sp_each(obj)
  obj.instance_variables.map {|v| "#{v}: #{obj.instance_variable_get(v)}\n"}.join
end
Run Code Online (Sandbox Code Playgroud)