2011-04-24 49 views
9
o = Object.new 
o.instance_eval { @str = "foo" } 
p o # => #<Object:0x5dd1a0 @foo="bar"> 

這很好。以對象作爲參數調用p會打印對象inspect方法的輸出。但不幸的是,如果對象有一個to_s方法,然後重寫它的輸出,輸出:仿真默認對象#inspect輸出?

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 
+0

您使用的是什麼版本的Ruby? – 2011-04-24 15:32:52

回答

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 ...>> 
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表示。

讓我調查是否有任何方式綁定到官方實現並使用它。

+0

「object_id」實際上是MRI中對象的'(void *)',在jruby中是類似的指針引用。用普通的紅寶石代碼無法訪問。這是...奇怪。 – 2011-07-26 05:10:53

7

讓數字更原始的實現匹配,你只需要左移一個位,它是作爲OBJECT_ID如下所示:

(object_id << 1).to_s(16) 

必須有一個額外的位用於標誌。