我有一个脚本,使用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::BasicObject
是Object
的子类。
我还没有找到任何方法来检测不是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的变化是我问题的最初原因。
答案 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)
优点:
缺点:
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]
中的注释
答案 7 :(得分:0)
对于类似的情况,您只希望从BasicObject
继承的类支持#class
方法,则可以从Kernel
复制该方法。
class Foo < BasicObject
define_method(:class, ::Kernel.instance_method(:class))
end
f = Foo.new
puts f.class
=> Foo