如何获取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::BasicObject
是Object
的子类。
我还没有找到任何方法来检测不是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的链接让我想到了这种方式。
优点:
- 它不需要外部库。
缺点:
- 必须在加载任何子类BasicObject的类之前执行它。
- 它为每个新类添加了一个方法
。
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