Ответ 1
Если вы можете перейти на Ruby 2.0, вам не нужно ничего реализовывать:
>> Kernel.instance_method(:class).bind(BasicObject.new).call
=> BasicObject
У меня есть script, который выполняет итерацию с помощью ObjectSpace#each_object
без аргументов. Затем он печатает, сколько экземпляров существует для каждого класса.
Я понял, что некоторые классы переопределяют метод экземпляра #class
, поэтому мне пришлось найти другой способ получить фактический класс; Скажем, он хранится в переменной "klass"
, а klass === object
- true.
В Ruby 1.8 я мог сделать это, предполагая, что Object
не был обезврежен обеими:
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
. Последнее делается для подкласса ::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__]
Идеи?
UPDATE:
Так как ruby 1.9 достиг конца жизни, я меняю свой прием на @indirect-ответ. Упоминания о рубине 1.9 выше приведены только для исторических целей, чтобы показать, что изменение от 1,8 до 1,9 было исходной причиной моей проблемы.
Если вы можете перейти на Ruby 2.0, вам не нужно ничего реализовывать:
>> Kernel.instance_method(:class).bind(BasicObject.new).call
=> BasicObject
Следующее решение относится к суперклассу eigenclass. Как следствие, он имеет побочный эффект для распределения eigenclass (обнаруживаемый ObjectSpace.count_objects[:T_CLASS]
в MRI). Но поскольку 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#===
, как предложил Кельвин на этой странице.
Я не знаю, как это сделать в Ruby, но это просто, используя API C для Ruby. RubyInline Gem значительно упрощает добавление битов C в ваш Ruby-код:
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
Ссылка fguillen заставляла меня думать об этом.
Плюсы:
Минусы:
.
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__ }
Это моя модификация ответа @pon:
Рассуждение за изменениями:
ActiveSupport::Duration
поведение экземпляра 2.seconds.class
остается
Fixnum
.Object
не имеет собственного метода __realclass__
, мы хотим избежать выделения eigenclass для этих экземпляров. Ответ на @paon original сделал это по определению, указав имя метода 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
Следующий код создает модуль 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]
Изменить: см. примечание в fooobar.com/info/258634/...