Ruby绑定 - 本地变量与常量的作用域

3

我希望能够运行字符串的eval函数来定义本地变量和常量。我想在不同的命名空间中执行此操作。我可以使用本地变量来完成此操作,但无法使用常量。是否有一种方法可以修改下面的NameSpaces模块,以便在一个绑定/命名空间中定义的常量不会被另一个绑定/命名空间看到?

# Example run under ruby 1.9.1
module NameSpaces
  def self.namespace(namespace)
    return binding
  end
end

b1 = NameSpaces.namespace("b1")
b2 = NameSpaces.namespace("b2")

# Set a 'x', then check to make sure its still set in the scope of 'b1'
puts b1.eval("x = 1") # => 1
puts b1.eval("x")     # => 1

# Check to make sure 'x' is NOT set in the scope of 'b2'
begin
  puts b2.eval("x") # NameError exception expected here
rescue Exception => e
  puts e.to_s       # => undefined local variable or method `x'
                    #  for NameSpaces:Module (THIS IS AS EXPECTED.)
end

# Set constant 'C' and do the same checks
puts b1.eval("C = 1") # => 1
puts b1.eval("C")     # => 1

# Check to make sure 'C' is NOT set in the scope of 'b2'
begin
  puts b2.eval("C")  # (I DON'T GET AN EXCEPTION.  NOT AS I HAD HOPED FOR.)
rescue Exception => e
  puts e.to_s
end

非常感谢您的关注。我遇到了非常困难的问题。
1个回答

5
您观察到的行为是正常的。当您在调用NameSpaces.namespace时执行C = 1,常量“C”将在NameSpaces上定义。(您可以通过尝试NameSpaces::C来确认这一点。)
为了获得您想要的效果,您需要使用匿名模块的绑定。尝试这个:
namespace1 = Module.new.class_eval("binding")
namespace2 = Module.new.class_eval("binding")
namespace1.eval("C = 1")
namespace1.eval("C")
=> 1
namespace2.eval("C")
NameError: uninitialized constant #<Module:0xf09180>::C

请注意,任何在Object(即全局范围)中定义的常量都将在传递给eval的代码中可用,如果这些常量的值在评估的代码中更改,则更改将在全局范围内可见!
(即使在BasicObject的上下文中评估代码(它不继承自Object),仍然可以通过在名称前加“::”前缀来访问在Object上定义的常量)

谢谢Alex。这很有启发性。我可能漏掉了什么,但我认为这种评估类型无法通过我的局部变量的第一个测试用例。我认为使用这种方法,我必须解析传递给我的程序的表达式以确定特定形式的eval。我希望的程序将接受所有最终用户提供的输入,并将其解析为表达式,但在用户自己的命名空间中而不是程序的命名空间中。 - dinman2022
@dinman2022,实际上,我用本地变量测试过了,它可以工作。 - Alex D
我不希望在调用之间本地变量失效。例如:`class MyClass def initialize @spaces = {} self.namespace = 'default' end def namespace=(id) @namespace = @spaces.has_key?(id) ? @spaces[id] : Module.new end def do_me(exp) if exp =~ /^namespace/ self.namespace = exp.split[1] else @namespace.class_eval(exp) end end endinput = %q{ x = 1 x # => DIES HERE namespace new_one x # should fail }c = MyClass.new input.each_line { |l| begin puts c.do_me(l) rescue Exception => e puts e end }` - dinman2022
@dinman2022,以 @namespace = 开头的那一行应该改为 @namespace = (@spaces.id[id] ||= Module.new.class_eval("binding"))。抱歉,我会修改我的回答。 - Alex D

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接