模拟默认对象#检查输出?

时间:2011-04-24 15:13:09

标签: ruby

o = Object.new
o.instance_eval { @str = "foo" }
p o # => #<Object:0x5dd1a0 @foo="bar">

这很好。使用对象作为参数调用p将打印对象inspect方法的输出。但是,不幸的是,如果对象覆盖了to_s方法,那么它将输出该输出:

class << o
  def to_s; @str; end
end
p o.to_s # => "foo"
p o # => foo

所以要解决这个问题,我们必须在对象上定义一个inspect方法:

class << o
  def inspect; "blah"; end
end
p o # => "blah"

如何让我的对象的inspect方法输出默认的Ruby方式,如第一个代码示例的第3行所示?

我最接近的是以下,但我不确定它是否正确

class << o
  def inspect
    vars = instance_variables.collect { |v| v.to_s << "=#{instance_variable_get(v).inspect}"}.join(", ")
    "#<#{self.class}:0x#{object_id} #{vars}>"
  end
end

3 个答案:

答案 0 :(得分:9)

要使数字与原始实现匹配,您只需要将object_id左移一位,如下所示:

(object_id << 1).to_s(16)

必须有一个额外的位用于标志。

答案 1 :(得分:5)

默认的inspect方法非常复杂,因为它需要正确处理对自身的递归调用。以下是基于Rubinius源代码的实现,忽略了to_s的存在。

module DefaultInspect

    Thread.current[:inspected_objects] = {}

    def inspected_objects
      Thread.current[:inspected_objects]
    end

    def inspect_recursion_guard
      inspected_objects[object_id] = true
      begin
        yield
      ensure
        inspected_objects.delete object_id
      end
    end

    def inspect_recursion?
      inspected_objects[object_id]    
    end

    def inspect
      prefix = "#<#{self.class}:0x#{self.__id__.to_s(16)}"

      # If it's already been inspected, return the ...
      return "#{prefix} ...>" if inspect_recursion?

      # Otherwise, gather the ivars and show them.
      parts = []

      inspect_recursion_guard do
        instance_variables.each do |var|
          parts << "#{var}=#{instance_variable_get(var).inspect}"
        end
      end

      if parts.empty?
        str = "#{prefix}>"
      else
        str = "#{prefix} #{parts.join(' ')}>"
      end

      str.taint if tainted?

      return str
    end

end

要使用此模块,您可以执行以下操作:

class Foo

  include DefaultInspect

  def to_s
    @foo
  end
end

f = Foo.new
f.instance_eval { @foo = f }
p f     #=> #<Foo:0x8042ad58 @foo=#<Foo:0x8042ad58 ...>>

答案 2 :(得分:0)

irb> o = Object.new.tap{ |o| o.instance_variable_set :@foo, "bar" }
#=> #<Object:0x00000102849600 @foo="bar">

irb> def o.to_s; @foo; end; o
#=> bar

irb> module MyInspect
irb>   def inspect
irb>     vars = instance_variables.map do |n|
irb>       "#{n}=#{instance_variable_get(n).inspect}"
irb>     end
irb>     "#<%s:0x%x %s>" % [self.class,object_id,vars.join(', ')]
irb>   end
irb> end

irb> o.extend MyInspect
#=> #<Object:0x81424b00 @foo="bar">

编辑:好吧,看起来我基本上想出了你已经做过的事情。尽管如此,你和我的两者都会导致不同的object_id表示。

让我调查是否有任何方式绑定到官方实现并使用它。