【问题标题】:Is there a hook for when anonymous classes are assigned to a constant?将匿名类分配给常量时是否有挂钩?
【发布时间】:2012-02-01 01:37:30
【问题描述】:

我最近一直在练习一些 Ruby 元编程,并且想知道 assigning anonymous classes to constants

在 Ruby 中,可以按如下方式创建匿名类:

anonymous_class = Class.new  # => #<Class:0x007f9c5afb21d0>

可以创建此类的新实例:

an_instance = anonymous_class.new # => #<#<Class:0x007f9c5afb21d0>:0x007f9c5afb0330>

现在,当匿名类被分配给一个常量时,该类现在有了一个正确的名称:

Foo = anonymous_class # => Foo

之前创建的实例现在也是该类的一个实例:

an_instance # => #<Foo:0x007f9c5afb0330>

我的问题:匿名类赋值给常量的那一刻有没有钩子方法?

Ruby中有很多hooks methods,但是我找不到这个。

【问题讨论】:

标签: ruby class hook metaprogramming constants


【解决方案1】:

匿名类在分配给常量时实际上并没有得到它们的名字。当他们接下来被问到他们的名字是什么时,他们实际上得到了它。

我会尝试为此找到参考。 编辑:找不到,抱歉。

【讨论】:

  • 关键是variable.c:305(ruby-1.9.3-p0)中定义的函数rb_class_name及其调用的函数(尤其是find_class_path,它使用RCLASS_CONST_TBL映射到检查类是否绑定到一个名称,至少如果我对它的解释是正确的)。
【解决方案2】:

让我们看看常量赋值在内部是如何工作的。下面的代码是从 ruby​​-1.9.3-p0 的源 tarball 中提取的。首先我们看VM指令setconstant(用于赋值常量)的定义:

# /insns.def, line 239
DEFINE_INSN
setconstant
(ID id)
(VALUE val, VALUE cbase)
()
{
  vm_check_if_namespace(cbase);
  rb_const_set(cbase, id, val);
  INC_VM_STATE_VERSION();
}

这里没有机会在vm_check_if_namespaceINC_VM_STATE_VERSION 中放置一个钩子。所以我们看rb_const_set(variable.c:1886),每次分配常量时调用的函数:

# /variable.c, line 1886
void
rb_const_set(VALUE klass, ID id, VALUE val)
{
    rb_const_entry_t *ce;
    VALUE visibility = CONST_PUBLIC;

    # ...

    check_before_mod_set(klass, id, val, "constant");
    if (!RCLASS_CONST_TBL(klass)) {
      RCLASS_CONST_TBL(klass) = st_init_numtable();
    }
    else {
      # [snip], won't be called on first assignment
    }

    rb_vm_change_state();

    ce = ALLOC(rb_const_entry_t);
    ce->flag = (rb_const_flag_t)visibility;
    ce->value = val;

    st_insert(RCLASS_CONST_TBL(klass), (st_data_t)id, (st_data_t)ce);
}

我删除了所有在模块内第一次分配常量时甚至没有调用的代码。然后我查看了这个函数调用的所有函数,没有找到可以从 Ruby 代码中放置钩子的地方。这意味着铁的事实是,除非我错过了什么,否则没有方法可以挂钩一个常量分配(至少在 MRI 中)。

更新

澄清:匿名类不会在分配后立即神奇地获得一个新名称(如安德鲁的回答中正确指出的那样)。相反,常量名称和类的对象 ID 存储在 Ruby 的内部常量查找表中。如果在那之后请求类的名称,现在可以将其解析为正确的名称(而不仅仅是Class:0xXXXXXXXX...)。

因此,对这个分配做出反应的最好方法是在后台工作线程的循环中检查类的name,直到它不是nil(这是对资源的巨大浪费,恕我直言)。

【讨论】:

  • 感谢您提供非常广泛的答案。为了完整起见,您是如何找到对setconstant 的引用的?源文件(insns.def)对我来说不是很清楚:“设置常量变量 id。如果 klass 为 Qfalse,则常量可以在此范围内访问。如果 klass 为 Qnil,则设置顶级常量。否则,在类或模块下设置常量。”这段代码是否确实用于将常量分配给(匿名)类实例?
  • @rdvdijk:我猜到并用printf验证了。
  • 我查看了您在此处引用的源代码,并理解了您粘贴的代码的含义。但是,它并没有完全回答我的问题。此代码显示了对常量分配的处理,并且此代码没有可用的挂钩。然而,这并没有解释匿名类何时何地发现它的新名称。按照 Andrew Grimm 的回答,classname 方法和相关代码似乎在 RCLASS_CONST_TBLrb_class_tbl 中查找常量,对吗?
  • 你的问题是Ruby是否提供了用于常量赋值的钩子方法,答案是否定的。我什至搜索了 Ruby 源代码来检查你是否可以通过一些讨厌的 hack 来解决这个问题,答案是否定的。您确实没有询问该类是如何获得新名称的。 Andrew Grimm 的回答和我对它的评论甚至回答了这个问题。关键功能是find_class_path。这意味着匿名类可以在分配后随时通过调用 inspect 来了解它的新名称。
  • 不必被冒犯!你的回答很好,我自然会接受。它比我预期的要广泛得多。
猜你喜欢
  • 2021-05-28
  • 2022-11-10
  • 1970-01-01
  • 2019-07-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-26
相关资源
最近更新 更多