【问题标题】:How do I get the class of a BasicObject instance?如何获取 BasicObject 实例的类?
【发布时间】:2012-02-08 16:38:49
【问题描述】:

我有一个使用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::DurationActiveSupport::BasicObject 的子类。后者是 Ruby 1.9 中 ::BasicObject 的子类,因此 Object 被排除在继承链之外。这在 Ruby 1.8 中不会也不会发生,因此 ActiveSupport::BasicObjectObject 的子类。

我还没有找到任何方法来检测不是Object 实例的Ruby 1.9 对象的实际类。 1.9 中的BasicObject 真的很简单:

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

想法?

更新:

由于 ruby​​ 1.9 已报废,我将接受更改为@indirect 的答案。上面提到的 ruby​​ 1.9 仅出于历史目的,以表明从 1.8 到 1.9 的更改是我的问题的原始原因。

【问题讨论】:

  • 我曾经遇到过同样的问题,我放弃了。有一个few approaches,但要么对我不起作用,要么过于打扰。也许您可以重新定义您的问题并直接指出希望您在起源中寻找而不是试图使一种可能的方法起作用。
  • @fguillen 感谢您的链接。关于使用 self.inherited 的帖子看起来很有希望。
  • 接受了 Frederick Cheung 的回答。我选择它而不是我的解决方案,因为它可能表现更好。其他人可能有不同的要求或限制 - 只需投票您喜欢的任何一个即可。
  • 转而接受 paon 的回答。它不依赖于外部库,唯一的缺点是它在您调用它的每个 BasicObject 上分配特征类。我要做的唯一更改是将方法定义为__realclass__ 而不是class
  • 仅供参考:根据 paon 的解决方案查看我的新答案。我保留了对 paon 的接受,因为核心思想是他的。

标签: ruby ruby-1.9


【解决方案1】:

以下解决方案是指特征类的超类。因此,它具有分配特征类的副作用(可通过 MRI 中的ObjectSpace.count_objects[:T_CLASS] 检测到)。但由于BasicObject#class 仅在空白石板对象上调用(即不是Object 的对象,即不是Objects),副作用也仅适用于空白石板对象。对于Objects, 调用标准的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

另一种不在Objects 上调用class &lt;&lt; self 的方法是通过Module#===,正如Kelvin 在此页面上所建议的那样。

【讨论】:

  • 这真是个天才……甚至可以在加载所有库后定义。但是你应该定义一个不同的方法名,比如__realclass__,否则20.seconds.class 不会返回Fixnum——这会破坏很多代码。如果你这样做,我会接受你的回答。
  • 顺便说一句,我尝试使用singleton_class.superclass,但我得到了TypeError: can't define singleton。猜猜 BasicObject 就是这么奇怪。
  • @Kelvin: Fixnums(以及其他立即数:falsetruenilSymbols)不是空白 slate 对象,因此 @987654345 @ 像以前一样为它们调用。使用singleton_class 不起作用,因为它是Kernel 的实例方法。
  • @Kelvin:::Object === self 测试 +1。我不知道Module#===Kernel#kind_of? 的倒数。
  • 我才意识到为什么会这样。这是因为单例类是一个Class 实例。 ClassObject 的子类,这就是为什么在实例上定义superclass 方法的原因。创建单例时,BasicObject 子类被设置为其超类。奇怪的红宝石魔法!
【解决方案2】:

如果你可以升级到 Ruby 2.0,你根本不需要实现任何东西:

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

【讨论】:

  • +1 是否有任何链接指向有关此功能为何有效的信息? BasicObject 不包括Kernel,那为什么Kernel 方法可以绑定到它呢?
  • 类似的技巧是:(class
  • +1,纯 Ruby 很棒,不需要其他任何东西 + 这适用于从 BasicObject 子类化的任何东西。不过同意@Kelvin,很高兴知道为什么会这样。
  • 回答@Kelvin 的问题:似乎所有模块方法都可以通过这种方式绑定到 any 对象。 MRI 的来源UnboundMethod#bind 包含一个抛出错误的保护子句,该子句开始于:if (!RB_TYPE_P(methclass, T_MODULE) &amp;&amp;...,然后继续测试对象是否为 kind_of? 未绑定方法的“所有者”类 - 很高兴知道。
  • 感谢@MatzFan。所以bind 的执行在 ruby​​ 2.x 中较弱。我想知道这种变化背后的基本原理是什么。
【解决方案3】:

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

【讨论】:

  • 当我读到这个问题时,我也在想一个inherited 钩子......很好的代码,我喜欢它!
【解决方案4】:

我不知道如何在 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 

【讨论】:

  • 这很优雅,似乎不会干扰应用程序的其他部分。我投了赞成票,但在接受之前需要考虑更多。
  • 顺便说一句,我认为在 FFI 中可能会有类似的东西,但我没有看到将 ruby​​ 对象转换为 VALUE 指针的方法,反之亦然。
  • RubyInline 中似乎有一个小错误 - 一旦创建了 ~/.ruby_inline 目录,我就无法在 irb 中定义更多的内联函数。 Inline 正在尝试获取 '(irb)' 的 mtime。从外部文件加载它可以正常工作。
【解决方案5】:

这是我对@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

【讨论】:

    【解决方案6】:
    (class << object; self; end).superclass
    

    【讨论】:

    • 是的,但这与接受的答案完全相同。
    【解决方案7】:

    以下代码通过复制Kernel 模块并随后删除除class 方法之外的所有方法来创建BasicKernel 模块。 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中的注释

    【讨论】:

    • 这是一个不错的概念,但没有解决ActiveSupport::Duration 问题,例如2.seconds.class应该返回 Fixnum。
    【解决方案8】:

    对于类似的情况,您只希望创建一个继承自 BasicObject 的类以支持 #class 方法,您可以从 Kernel 复制该方法。

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

    【讨论】:

      猜你喜欢
      • 2018-12-10
      • 1970-01-01
      • 1970-01-01
      • 2012-12-30
      • 1970-01-01
      • 2016-02-05
      • 1970-01-01
      • 1970-01-01
      • 2021-03-28
      相关资源
      最近更新 更多