如何获取BasicObject实例的类?

时间:2012-02-08 16:38:49

标签: ruby ruby-1.9

我有一个脚本,使用ObjectSpace#each_object进行迭代,没有args。然后它打印每个类存在多少个实例。

我意识到有些类重新定义了#class实例方法,所以我必须找到另一种获取实际类的方法;假设它存储在变量"klass"中,klass === object为真。

在Ruby 1.8中我可以这样做,假设Object没有被monkeypatched:

Object.instance_method(:class).bind(object).call

这适用于ActiveSupport::Duration个实例:

# Ruby 1.8
# (tries to trick us)
20.seconds.class
=> Fixnum
# don't try to trick us, we can tell
Object.instance_method(:class).bind(20.seconds).call
=> ActiveSupport::Duration

但是,在Ruby 1.9中,这不再适用:

# Ruby 1.9
# we are not smart...
Object.instance_method(:class).bind(20.seconds).call
TypeError: bind argument must be an instance of Object
  from (irb):53:in `bind'
  from (irb):53
  from /Users/user/.rvm/rubies/ruby-1.9.2-p0/bin/irb:17:in `<main>'

结果是ActiveSupport::Duration子类ActiveSupport::BasicObject。后者在Ruby 1.9中是子类::BasicObject,因此Object被排除在继承链之外。这不会,也不会发生在Ruby 1.8中,因此ActiveSupport::BasicObjectObject的子类。

我还没有找到任何方法来检测不是Object实例的Ruby 1.9对象的实际类。 1.9中的BasicObject非常简单:

BasicObject.instance_methods
=> [:==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__]

想法?

更新:

由于ruby 1.9达到了寿命终止,我正在改变我对@ indirect的回答。上面提到的红宝石1.9只是出于历史目的,表明从1.8到1.9的变化是我问题的最初原因。

8 个答案:

答案 0 :(得分:12)

以下解决方案涉及本征类的超类。因此,它具有分配特征类的副作用(在MRI中可由ObjectSpace.count_objects[:T_CLASS]检测到)。但是,因为BasicObject#class仅在空白平板对象上调用(即不是Object种类的对象,即不是Object s 的副作用也适用于空白的石板对象。对于Object s

。标准Kernel#class被调用。

class BasicObject
  def class
    (class << self; self end).superclass
  end
end

# tests:
puts RUBY_VERSION               # 1.9.2
class B < BasicObject; end
class X;               end
p BasicObject.new.class             # BasicObject
p B          .new.class             # B
p X          .new.class             # X
p               6.class             # Fixnum
p B.instance_method(:class).owner   # BasicObject
p X.instance_method(:class).owner   # Kernel
p          6.method(:class).owner   # Kernel

编辑 - 注意: 确实,ActiveSupport::Duration存在问题。此类使用拦截(method_missing)将消息重定向到:value属性。因此,它为其实例提供了错误的内省。为了保持这种虚假性,有必要为类地图使用另一个名称,例如建议的__realclass__。因此,修改后的解决方案可能如下所示:

class BasicObject
  def __realclass__; (class << self; self end).superclass end
end
class Object; alias __realclass__ class end

另一种不在class << self上调用Object的方法是通过Module#===,正如Kelvin在此页面上所建议的那样。

答案 1 :(得分:7)

如果您可以升级到Ruby 2.0,则根本不需要实现任何内容:

>> Kernel.instance_method(:class).bind(BasicObject.new).call
=> BasicObject

答案 2 :(得分:5)

我不知道在Ruby中这样做,但使用C API到Ruby很简单。 RubyInline Gem使得向Ruby代码添加C位非常简单:

require 'inline'
class Example
  inline do |builder|  
    builder.c_raw_singleton <<SRC, :arity => 1
      VALUE true_class(VALUE self, VALUE to_test) {
        return rb_obj_class(to_test);
      }
SRC
   end
end

然后:

1.9.2p180 :033 > Example.true_class(20.minutes)
 => ActiveSupport::Duration 

答案 3 :(得分:5)

fguillen的链接让我想到了这一点。

优点:

  1. 它不需要外部库。
  2. 缺点:

    1. 必须在加载任何子类BasicObject的类之前执行。
    2. 它为每个新类添加了一个方法
    3. class BasicObject
        def self.inherited(klass)
          klass.send(:define_method, :__realclass__) { klass }
        end
        def __realclass__
          BasicObject
        end
      end
      
      # ensures that every Object will also have this method
      class Object
        def __realclass__
          Object.instance_method(:class).bind(self).call
        end
      end
      
      require 'active_support/core_ext'
      
      20.seconds.__realclass__  # => ActiveSupport::Duration
      
      # this doesn't raise errors, so it looks like all objects respond to our method
      ObjectSpace.each_object{|e| e.__realclass__ }
      

答案 4 :(得分:3)

这是我对@ paon答案的修改:

改变背后的推理:

  • 方法名称不会与现有的库发生冲突,例如该 ActiveSupport::Duration实例行为2.seconds.class仍然存在 Fixnum
  • 由于Object没有自己的__realclass__方法,我们希望避免为这些实例分配本征类。 @ paon的原始答案通过定义class方法名称来实现这一点。

class BasicObject
  def __realclass__
    ::Object === self ?
      # Note: to be paranoid about Object instances, we could 
      # use Object.instance_method(:class).bind(s).call.
      self.class :
      (class << self; self end).superclass
  end
end

# test
require 'active_support/core_ext/integer'
require 'active_support/core_ext/numeric'

duration = 2.seconds
string = 'hello world'
p duration.class  # => Fixnum
p string.class    # => String
GC.start
p ObjectSpace.count_objects[:T_CLASS]  # => 566

# creates the eigenclass
p duration.__realclass__  # => ActiveSupport::Duration
p ObjectSpace.count_objects[:T_CLASS]  # => 567

# doesn't create the eigenclass
p string.__realclass__  # => String
p ObjectSpace.count_objects[:T_CLASS]  # => 567

答案 5 :(得分:1)

(class << object; self; end).superclass

答案 6 :(得分:0)

以下代码通过复制BasicKernel模块创建Kernel模块,然后删除除class方法之外的所有方法。 BasicKernel类中包含BasicObject(就像Kernel中包含Object一样)。

req_methods中,您可以指定要保留的Kernel方法的任意子集。

class BasicObject
  include ::BasicKernel = ::Kernel.dup.module_eval {
    v = $VERBOSE
    $VERBOSE = nil               # suppress object_id warning
    req_methods = [:class]       # required methods (to be preserved)
    all_methods = public_instance_methods +
               protected_instance_methods +
                 private_instance_methods
    all_methods.each { |x| remove_method(x) unless req_methods.include?(x) }
    $VERBOSE = v
    self
  }
end

# tests:
puts RUBY_VERSION               # 1.9.2
class B < BasicObject; end
class X;               end
p BasicObject.new.class           # BasicObject
p B          .new.class           # B
p X          .new.class           # X
p B.instance_method(:class).owner # BasicKernel
p X.instance_method(:class).owner # Kernel
p Object.ancestors                # [Object, Kernel, BasicObject, BasicKernel]
p BasicKernel.instance_methods    # [:class]

修改:请参阅https://stackoverflow.com/a/10216927/641718

中的注释

答案 7 :(得分:0)

对于类似的情况,您只希望从BasicObject继承的类支持#class方法,则可以从Kernel复制该方法。

class Foo < BasicObject
  define_method(:class, ::Kernel.instance_method(:class))
end

f = Foo.new
puts f.class
=> Foo