2

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

@x: 1
@y: 2
@link_to_point: #<Point:0x10031b298 @y=20, @x=38>

更新: 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
4

4 回答 4

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

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

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

编辑 2:这并没有真正为您的问题的答案添加任何内容,但是:只是为了证明我们在重新实现中没有丢失任何东西,这里是all_variables根据each_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

我觉得它应该更短,但这可能是不可能的;因为我们现在没有递归,所以我们必须显式地维护堆栈(in parents)。但这是可能的,所以该each_variable方法同样有效(我认为它更好一点)。

于 2010-06-17T00:14:07.887 回答
5

我明白了... Antal 必须在这里提供高级版本...

那么简短的版本可能是:

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

或将其作为字符串返回:

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

或更短:

def sp_each(obj)
  obj.instance_variables.map {|v| "#{v}: #{obj.instance_variable_get(v)}\n"}.join
end
于 2010-06-17T01:56:06.940 回答
1

这是我为另一个问题编写的简单 JSON 发射器的快速改编:

class Object
  def inspect!(indent=0)
    return inspect if instance_variables.empty?
    "#<#{self.class}:0x#{object_id.to_s(16)}\n#{'  ' * indent+=1}#{
      instance_variables.map {|var|
        "#{var}: #{instance_variable_get(var).inspect!(indent)}"
      }.join("\n#{'  ' * indent}")
    }\n#{'  ' * indent-=1}>"
  end
end

class Array
  def inspect!(indent=0)
    return '[]' if empty?
    "[\n#{'  ' * indent+=1}#{
      map {|el| el.inspect!(indent) }.join(",\n#{'  ' * indent}")
    }\n#{'  ' * indent-=1}]"
  end
end

class Hash
  def inspect!(indent=0)
    return '{}' if empty?
    "{\n#{'  ' * indent+=1}#{
      map {|k, v|
        "#{k.inspect!(indent)} => #{v.inspect!(indent)}"
      }.join(",\n#{'  ' * indent}")
    }\n#{'  ' * indent-=1}}"
  end
end

这就是所有的魔法,真的。现在我们只需要一些简单的默认值来处理完全检查没有意义的某些类型(nil, false, true, 数字等):

module InspectBang
  def inspect!(indent=0)
    inspect
  end
end

[Numeric, Symbol, NilClass, TrueClass, FalseClass, String].each do |klass|
  klass.send :include, InspectBang
end
于 2010-06-17T11:50:23.017 回答
0

像这样?

# Get the instance variables of an object
d = Date.new
d.instance_variables.each{|i| puts i + "<br />"}

instance_variables 上的 Ruby 文档

这个概念通常被称为“内省”,(审视自己)。

于 2010-06-16T23:15:21.647 回答