ruby ruby-1.9

ruby - ¿Cómo obtengo la clase de una instancia de BasicObject?



ruby-1.9 (8)

Tengo un script que itera usando ObjectSpace#each_object sin ObjectSpace#each_object . Luego imprime cuántas instancias existen para cada clase.

Me di cuenta de que algunas clases redefinían el método de instancia #class , por lo que tenía que encontrar otra forma de obtener la clase real; Digamos que está almacenado en la variable "klass" , y el klass === object es verdadero.

En Ruby 1.8, podría hacer esto, asumiendo que Object no fue pareado:

Object.instance_method(:class).bind(object).call

Esto funcionó para las ActiveSupport::Duration de 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

Pero, en Ruby 1.9 esto ya no funciona:

# 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>''

Resulta que las subclases ActiveSupport::BasicObject ActiveSupport::Duration ActiveSupport::BasicObject . El último se hace a la subclase ::BasicObject en Ruby 1.9, por lo que el Object se excluye de la cadena de herencia. Esto no sucede y no puede suceder en Ruby 1.8, por lo que ActiveSupport::BasicObject es una subclase de Object .

No he encontrado ninguna forma de detectar la clase real de un objeto Ruby 1.9 que no sea una instancia de Object . BasicObject en 1.9 es realmente BasicObject :

BasicObject.instance_methods => [:==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__]

Ideas?

ACTUALIZAR:

Desde que Ruby 1.9 llegó al final de su vida útil, estoy cambiando mi aceptación a la respuesta de @ indirect. Las menciones de ruby ​​1.9 anteriores son meramente con fines históricos, para mostrar que el cambio de 1.8 a 1.9 fue la causa original de mi problema.


El enlace de Fguillen me hizo pensar de esta manera.

Pros:

  1. No necesita librerías externas.

Contras:

  1. Debe ejecutarse antes de cargar cualquier clase que subclase BasicObject.
  2. Agrega un método a cada nueva clase.

.

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__ }


El siguiente código crea un módulo BasicKernel mediante la duplicación del módulo Kernel y la posterior eliminación de todos los métodos, excepto el método de class . El BasicKernel está incluido en la clase BasicObject (al igual que Kernel está incluido en Object ).

En req_methods , puede especificar un subconjunto arbitrario de métodos de Kernel para preservar.

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]

Edición: consulte la nota en https://.com/a/10216927/641718


Esta es mi modificación de la respuesta de @Paon:

Razonamiento detrás de los cambios:

  • El nombre del método no choca con las bibliotecas existentes, por ejemplo, el comportamiento de la instancia de ActiveSupport::Duration 2.seconds.class sigue siendo Fixnum .
  • Como Object no tiene su propio método __realclass__ , queremos evitar la asignación de la eigenclass para esas instancias. La respuesta original de @ paon hizo esto inherentemente al definir el nombre del método de 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


La siguiente solución se refiere a la superclase de eigenclass. Como consecuencia, tiene el efecto secundario de asignar la eigenclass (detectable por ObjectSpace.count_objects[:T_CLASS] en MRI). Pero como la BasicObject#class solo se invoca en objetos de pizarra en blanco (es decir, los objetos que no son objetos de tipo, es decir, que no son Object ), el efecto secundario también se aplica solo a los objetos de pizarra en blanco. Para los Object s , se invoca la Kernel#class estándar de 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

Editar - Nota: De hecho, hay un problema con ActiveSupport::Duration . Esta clase usa intercepción ( method_missing ) para redirigir mensajes al atributo :value . Como consecuencia, proporciona falsa introspección para sus instancias. Para preservar esta falsedad, es necesario usar otro nombre para el mapa de clase, por ejemplo, el __realclass__ propuesto. Por lo tanto, la solución modificada podría tener este aspecto:

class BasicObject def __realclass__; (class << self; self end).superclass end end class Object; alias __realclass__ class end

Otra forma de no invocar la class << self en Object s es a través del Module#=== , como sugiere Kelvin en esta página.


No sé cómo hacerlo en Ruby, pero es sencillo usar la API de C para Ruby. La gema RubyInline hace que agregar bits de C a tu código Ruby sea bastante fácil:

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

Y entonces:

1.9.2p180 :033 > Example.true_class(20.minutes) => ActiveSupport::Duration


Para la situación similar en la que simplemente desea que una clase que creó que hereda de BasicObject compatible con el método #class , puede copiar el método desde Kernel .

class Foo < BasicObject define_method(:class, ::Kernel.instance_method(:class)) end f = Foo.new puts f.class => Foo


Si puedes actualizar a Ruby 2.0, no necesitas implementar nada:

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


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