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

发布于 2024-09-06 10:27:51 字数 671 浏览 12 评论 0原文

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

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

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

我还希望能够将
打印到每个实例变量值的末尾,以便在网页上很好地打印它们。

现在的困难似乎是不是每个实例变量都有访问器,因此不能用 obj.send(var_name) 调用它

(var_name 删除了“@”,因此“@x”变成了“x”)

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

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

Given any object in Ruby (on Rails), how can I write a method so that it will display that object's instance variable names and its values, like this:

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

(Update: inspect will do except for large object it is difficult to break down the variables from the 200 lines of output, like in Rails, when you request.inspect or self.inspect in the ActionView object)

I also want to be able to print <br> to the end of each instance variable's value so as to print them out nicely on a webpage.

the difficulty now seems to be that not every instance variable has an accessor, so it can't be called with obj.send(var_name)

(the var_name has the "@" removed, so "@x" becomes "x")

Update: I suppose using recursion, it can print out a more advanced version:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

旧情勿念 2024-09-13 10:27:51

我可能会这样写:

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 只是一个方便的包装器。因此,printspect_variables(foo)为您提供了一个换行符分隔的选项,而printinspect_variables(foo, "
gt;\n")
为您提供了版本带有 HTML 换行符。如果您想指定缩进,也可以这样做: printspectr_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

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

I would probably write it like this:

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

The Object#all_variables method produces an array containing (0) the given object and (1) a hash mapping instance variable names to arrays containing (0) the instance variable and (1) a hash mapping…. Thus, it gives you a nice recursive structure. The string_variables function prints out that hash nicely; inspect_variables is just a convenience wrapper. Thus, print inspect_variables(foo) gives you a newline-separated option, and print inspect_variables(foo, "<br />\n") gives you the version with HTML line breaks. If you want to specify the indent, you can do that too: print inspect_variables(foo, "\n", "|---") produces a (useless) faux-tree format instead of tab-based indenting.

There ought to be a sensible way to write an each_variable function to which you provide a callback (which wouldn't have to allocate the intermediate storage); I'll edit this answer to include it if I think of something. Edit 1: I thought of something.

Here's another way to write it, which I think is slightly nicer:

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

The Object#each_variable method takes a number of optional arguments, which are not designed to be specified by the user; instead, they are used by the recursion to maintain state. The given block is passed (a) the name of the instance variable, or nil if the variable is the root of the recursion; (b) the variable; (c) the depth to which the recursion has descended; and (d), the parent of the current variable, or nil if said variable is the root of the recursion. The recursion is depth-first. The inspect_variables function uses this to build up a string. The obj argument is the object to iterate through; nl is the line separator; indent is the indentation to be applied at each level; and sep separates the name and the value.

Edit 2: This doesn't really add anything to the answer to your question, but: just to prove that we haven't lost anything in the reimplementation, here's a reimplementation of all_variables in terms of 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

I feel like it ought to be shorter, but that may not be possible; because we don't have the recursion now, we have to maintain the stack explicitly (in parents). But it is possible, so the each_variable method works just as well (and I think it's a little nicer).

[浮城] 2024-09-13 10:27:51

我明白了... 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

I see... Antal must be giving the advanced version here...

the short version then probably is:

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

or to return it as a string:

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

or shorter:

def sp_each(obj)
  obj.instance_variables.map {|v| "#{v}: #{obj.instance_variable_get(v)}\n"}.join
end
何其悲哀 2024-09-13 10:27:51

这是一个简单的 我为另一个问题编写的 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

这就是所有的魔力,真的。现在,我们只需要对某些类型进行一些简单的默认设置,在这些类型中,全面检查实际上没有意义(nilfalsetrue、数字等):

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

[Numeric, Symbol, NilClass, TrueClass, FalseClass, String].each do |klass|
  klass.send :include, InspectBang
end

This is a quick adaptation of a simple JSON emitter I wrote for another question:

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

That's all the magic, really. Now we only need some simple defaults for some types where a full-on inspect doesn't really make sense (nil, false, true, numbers, etc.):

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

[Numeric, Symbol, NilClass, TrueClass, FalseClass, String].each do |klass|
  klass.send :include, InspectBang
end
喜爱纠缠 2024-09-13 10:27:51

像这样?

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

有关instance_variables的Ruby文档

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

Like this?

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

Ruby Documentation on instance_variables.

The concept is commonly called "introspection", (to look into oneself).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文