如何获取BasicObject实例的类?

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

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

在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 `' 

事实certificate, ActiveSupport::Duration ActiveSupport::BasicObject子类。 后者在Ruby 1.9中是子类::BasicObject ,因此Object被从inheritance链中排除。 这不会,也不会发生在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的回答。 上面提到的ruby1.9只是出于历史目的,表明从1.8到1.9的变化是我问题的最初原因。

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

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

以下解决方案涉及本征类的超类。 因此,它具有分配本征类的副作用(可通过MRI中的ObjectSpace.count_objects[:T_CLASS]检测)。 但由于BasicObject#class仅在空白平板对象上调用(即不是Object类型的Object ,即不是Object ),因此副作用也仅适用于空白平板对象。 对于Object ,调用标准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 

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

fguillen的链接让我想到了这种方式。

优点:

  1. 它不需要外部库。

缺点:

  1. 必须在加载任何子类BasicObject的类之前执行它。
  2. 它为每个新类添加了一个方法

 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__ } 

我不知道在Ruby中这样做,但使用C API到Ruby是很简单的。 RubyInline Gem可以很容易地为你的Ruby代码添加C:

 require 'inline' class Example inline do |builder| builder.c_raw_singleton < 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 

这是我对@ 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 
 (class << object; self; end).superclass 

以下代码通过复制Kernel模块并随后删除除class方法之外的所有方法来创建BasicKernel模块。 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中的注释

对于类似的情况,您只需要创建一个inheritance自BasicObject来支持#class方法,您可以从Kernel复制该方法。

 class Foo < BasicObject define_method(:class, ::Kernel.instance_method(:class)) end f = Foo.new puts f.class => Foo