如何获取BasicObject实例的类?

Kel*_*vin 21 ruby ruby-1.9

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

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

在Ruby 1.8中我可以做到这一点,假设Object没有monkeypatched:

Object.instance_method(:class).bind(object).call
Run Code Online (Sandbox Code Playgroud)

这适用于以下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
Run Code Online (Sandbox Code Playgroud)

但是,在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>'
Run Code Online (Sandbox Code Playgroud)

事实证明,这是ActiveSupport::Duration子类ActiveSupport::BasicObject.后者::BasicObject在Ruby 1.9中是子类,因此Object从继承链中排除.这不会,也不会发生在Ruby 1.8中,因此ActiveSupport::BasicObject是它的子类Object.

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

BasicObject.instance_methods
=> [:==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__]
Run Code Online (Sandbox Code Playgroud)

想法?

更新:

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

pao*_*aon 13

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

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

class BasicObject
  def __realclass__; (class << self; self end).superclass end
end
class Object; alias __realclass__ class end
Run Code Online (Sandbox Code Playgroud)

不调用的另一种方式class << selfObjectS是通过Module#===,因为这个页面上所建议的开尔文.


ind*_*ect 7

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

>> Kernel.instance_method(:class).bind(BasicObject.new).call
=> BasicObject
Run Code Online (Sandbox Code Playgroud)

  • 回答@Kelvin的问题:似乎所有模块方法都可以通过这种方式绑定到_any_对象.MRI的来源[UnboundMethod#bind](http://ruby-doc.org/core-2.3.1/UnboundMethod.html#method-i-bind)包含一个用于抛出错误的保护子句:`if(! RB_TYPE_P(methclass,T_MODULE)&& ...`继续测试对象是否是`kind_of?`未绑定方法的'owner'类 - 很高兴知道. (2认同)

Fre*_*ung 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
Run Code Online (Sandbox Code Playgroud)

然后:

1.9.2p180 :033 > Example.true_class(20.minutes)
 => ActiveSupport::Duration 
Run Code Online (Sandbox Code Playgroud)


Kel*_*vin 5

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__ }
Run Code Online (Sandbox Code Playgroud)