Как получить класс экземпляра BasicObject? - PullRequest
21 голосов
/ 08 февраля 2012

У меня есть скрипт, который повторяется с использованием ObjectSpace#each_object без аргументов.Затем он печатает, сколько экземпляров существует для каждого класса.

Я понял, что некоторые классы переопределяют метод экземпляра #class, поэтому мне пришлось искать другой способ получить реальный класс;Допустим, он хранится в переменной "klass", а klass === object - это истина.

В 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__]

Идеи?

ОБНОВЛЕНИЕ:

Поскольку ruby ​​1.9 достиг конца срока службы, я меняюсья принимаю @ косвенный ответ.Упомянутые выше рубины 1.9 приведены исключительно для исторических целей, чтобы показать, что первоначальное возникновение моей проблемы было изменением с 1.8 на 1.9.

Ответы [ 8 ]

13 голосов
/ 18 апреля 2012

Следующее решение относится к суперклассу собственного класса. Как следствие, он имеет побочный эффект выделения собственного класса (обнаруживается с помощью ObjectSpace.count_objects[:T_CLASS] в МРТ). Но поскольку BasicObject#class вызывается только для объектов чистого листа (т. Е. Объектов, которые не относятся к типу Object, т.е. не являются Object s ), побочный эффект также применяется только для объектов чистого листа. , Для 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

Другой способ не вызывать class << self на Object s - через Module#===, как предлагает Кельвин на этой странице.

7 голосов
/ 04 сентября 2013

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

>> Kernel.instance_method(:class).bind(BasicObject.new).call
=> BasicObject
5 голосов
/ 08 февраля 2012
Ссылка

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__ }
5 голосов
/ 08 февраля 2012

Я не знаю, как это сделать в Ruby, но это просто, если использовать C API для 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 
3 голосов
/ 08 мая 2012

Это моя модификация ответа @ 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
1 голос
/ 18 июня 2014
(class << object; self; end).superclass
0 голосов
/ 24 августа 2018

Для аналогичной ситуации, когда вы просто хотите, чтобы созданный вами класс, который наследовал от BasicObject для поддержки метода #class, вы можете скопировать метод из Kernel.

class Foo < BasicObject
  define_method(:class, ::Kernel.instance_method(:class))
end

f = Foo.new
puts f.class
=> Foo
0 голосов
/ 22 апреля 2012

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

Редактировать: см. Примечание в https://stackoverflow.com/a/10216927/641718

...