Подтвердить что ты не робот

Как получить класс экземпляра 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 было исходной причиной моей проблемы.

4b9b3361

Ответ 1

Если вы можете перейти на Ruby 2.0, вам не нужно ничего реализовывать:

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

Ответ 2

Следующее решение относится к суперклассу 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#===, как предложил Кельвин на этой странице.

Ответ 3

Я не знаю, как это сделать в 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 

Ответ 4

Ссылка 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__ }

Ответ 5

Это моя модификация ответа @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

Ответ 6

(class << object; self; end).superclass

Ответ 7

Следующий код создает модуль 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/...