Ruby:定义类变量时 class_exec 出现意外结果

标签 ruby

在 Ruby 中,当使用 class_exec 定义类的内容时,我得到了意外的结果。当我在发送到 class_exec 的 block 中定义类变量时,该类变量是在 Object 上定义的,而不是在 class_exec 所在的类上定义的。被调用:

class X; end
X.class_exec do
  @@inner_value = "123"
  def inner_value
    @@inner_value
  end
  def inner_value=(arg)
    @@inner_value = arg
  end
end

obj1 = X.new
puts obj1.inner_value
puts @@inner_value
puts Object.class_variables

产品:

123
123
@@inner_value

使用class_eval时不会发生这种情况:

X.class_eval(<<-RUBY)
  @@inner_value = "123"
  def inner_value
    @@inner_value
  end
  def inner_value=(arg)
    @@inner_value = arg
  end
RUBY

obj1 = X.new
puts obj1.inner_value
puts @@inner_value
puts Object.class_variables

产品:

123

和一个错误:

uninitialized class variable @@inner_value in Object (NameError)

class_eval 的结果是我期望在这两种情况下发生的结果。我已在 MRI 1.8.7 和 MRI 1.9.3 上尝试过此操作,并在 Windows XP 上运行时得到相同的结果。

这是预期的行为吗?如果是这样,为什么?如果没有,有bug吗?

最佳答案

类变量在编译时绑定(bind)到声明它们的类。传递给 class_exec 的 block 在传递给 class_exec 之前进行编译,因此类变量绑定(bind)到 Object

我猜你的 class_exec 位于顶层,位于 Object 中,所以这就是它们所在的位置。演示:

public

class Object
    @@x = "ribbit"
end

def foo
    puts "test: #{@@x}"
end

x = Object.new
x.foo

这就是为什么当您在模块中使用类变量时,包含该模块的所有类(通过包含的方法)将看到相同的类变量。类变量绑定(bind)到模块。如果你运行这个:

class WithClassVars
    def self.classvars
        @classvars ||= {}
    end

    def classvars
        self.class.classvars
    end
end

class A < WithClassVars;end
class B < WithClassVars;end

a = A.new
b = B.new
a.classvars[:a] = 1
b.classvars[:a] = 2

puts a.classvars
puts b.classvars

a 和 b 最终会得到相同的数据。

如果您将代码作为字符串传递给 class_eval,该字符串将在 class_eval 中编译,因此您可以确保它们位于那么就选择正确的类。

因此,如果您想存储每个类的数据,则必须使用 class_eval,或者使用某种机制来使用类的实例变量。说:

class WithClassVars
    def self.classvars
        @classvars ||= {}
    end

    def classvars
        self.class.classvars
    end
end

class A < WithClassVars;end
class B < WithClassVars;end

a = A.new
b = B.new
a.classvars[:a] = 1
b.classvars[:a] = 2

puts a.classvars
puts b.classvars

关于Ruby:定义类变量时 class_exec 出现意外结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10109925/

相关文章:

ruby-on-rails - 停止 Rails 控制台在循环结束时打印出对象

ruby - 如何在 Ruby + Grape API 中使所有时间输出成为 ISO 8601

ruby-on-rails - 给定一个带句号的输入,如何让所有内容都跟在它后面?

Ruby:Titleize:如何忽略较小的词,如 'and' 、 'the' 、 'or 等

ruby-on-rails - 将 float 舍入到 Ruby 中最近的四分之一

ruby - 当 shelled-out 命令返回非零退出代码时,如何让 Ruby 脚本失败?

ruby - 如何将数据通过管道传输到通过标准输入上的 Net::SSH 启动的进程?

ruby - 永久设置 "rvm use ruby-1.9.3"?

ruby-on-rails - 从键创建新的数组数组

ruby - 如何使用 Groovy 将字段动态添加到 Java 类?