如何动态创建一个局部变量?

我有一个变量var = "some_name" ,我想创建一个新的对象,并将其分配给some_name 。 我该怎么做? 例如

 var = "some_name" some_name = Struct.new(:name) # I need this a = some_name.new('blah') # so that I can do this. 

你不能在Ruby 1.9中动态创建局部变量(你可以通过eval在Ruby 1.8中):

 eval 'foo = "bar"' foo # NameError: undefined local variable or method `foo' for main:Object 

它们可以在评估代码中使用,但是:

 eval 'foo = "bar"; foo + "baz"' #=> "barbaz" 

Ruby 2.1添加了local_variable_set ,但是不能创建新的局部变量:

 binding.local_variable_set :foo, 'bar' foo # NameError: undefined local variable or method `foo' for main:Object 

如果不修改Ruby本身, 则不能更改此行为。 另一种方法是考虑将数据存储在另一个数据结构中,例如Hash,而不是许多局部变量:

 hash = {} hash[:my_var] = :foo 

请注意, evallocal_variable_set 允许重新分配现有的本地变量:

 foo = nil eval 'foo = "bar"' foo #=> "bar" binding.local_variable_set :foo, 'baz' foo #=> "baz" 

说到ruby 2.2.x的确,你不能在当前上下文/绑定中以编程方式创建局部变量,但是你可以在一些特定的绑定中设置变量。

 b = binding b.local_variable_set :gaga, 5 b.eval "gaga" => 5 

有趣的是, binding调用每次都会给你一个新的绑定。 所以你需要得到你感兴趣的绑定的句柄,然后在需要的变量被设置之后在它的上下文中进行评估。

这有用吗? 例如,我想评估ERB,如果你可以使用<%= myvar %>而不是<%= opts[:myvar] %>或者类似的话,那么编写ERB会更好。

创建一个新的绑定我正在使用一个模块类的方法(我敢肯定有人会纠正我如何正确调用这个,在java中我将它称为一个静态方法)来获得一个干净的绑定与特定的变量集:

 module M def self.clean_binding binding end def self.binding_from_hash(**vars) b = self.clean_binding vars.each do |k, v| b.local_variable_set k.to_sym, v end return b end end my_nice_binding = M.binding_from_hash(a: 5, **other_opts) 

现在你只有所需的变量绑定。 您可以使用它来更好地控制ERB或其他(可能是第三方) 可信代码(这不是任何类型的沙箱)的评估。 这就像定义一个接口。

更新:关于绑定的一些额外的注意事项。 你创建它们的地方也会影响方法和常量解析的可用性。 在上面的例子中,我创建了一个合理的干净的绑定。 但是,如果我想提供一些对象的实例方法,我可以通过类似的方法创建一个绑定,但在该对象的类中。 例如

 module MyRooTModule class Work def my_instance_method ... end def not_so_clean_binding binding end end class SomeOtherClass end end 

现在我的my_object.not_so_clean_binding将允许代码在my_object对象上调用#my_instance_method 。 以相同的方式,您可以使用此绑定而不是MyRootModule::SomeOtherClass.new在代码中MyRootModule::SomeOtherClass.new 。 所以在创建一个绑定时,有时需要更多的考虑,而不仅仅是局部变量。 HTH

虽然,正如其他人所指出的,你不能在Ruby中动态地创建局部变量,但是你可以用一些方法来模拟这种行为:

 hash_of_variables = {var1: "Value 1", var2: "Value 2"} hash_of_variables.each do |var, val| define_method(var) do instance_variable_get("@__#{var}") end instance_variable_set("@__#{var}", val) end puts var1 puts var2 var1 = var2.upcase puts var1 

打印:

 Value 1 Value 2 VALUE 2 

有些库将这种技术与instance_exec结合起来,以显示块内部似乎是局部变量的内容:

 def with_vars(vars_hash, &block) scope = Object.new vars_hash.each do |var, val| scope.send(:define_singleton_method, var) do scope.instance_variable_get("@__#{var}") end scope.instance_variable_set("@__#{var}", val) end scope.instance_exec(&block) end with_vars(a: 1, b:2) do puts a + b end 

打印:3

请注意,抽象并不完美:

 with_vars(a: 1, b:2) do a = a + 1 puts a end 

结果是: undefined method `+' for nil:NilClass 。 这是因为a=定义了一个实际的局部变量,初始化为nil ,优先于方法a 。 然后a.+(1)被调用,而nil没有+方法,所以抛出一个错误。

所以尽管这个方法对于模拟只读本地变量非常有用,但是当您尝试在块内重新分配变量时,它并不总是奏效。

其他人写道,你不能在本地环境中动态地声明真正的变量。 然而,你可以通过对象属性实现类似的功能,因为在Ruby世界中,一切都是一个对象(甚至主要上下文),你可以很容易地使用新的属性来扩展这些对象。 corse,这个操作可以动态完成。 我们来看看这个方法。

首先,我们来看看irb的主要范围。

 > self => main > self.class => Object > self.class.ancestors => [Object, Kernel, BasicObject] 

正如你现在所看到的, main是一个真正的对象。 对象可以具有与变量具有相同间接属性的属性。 通常,当声明新类时,我们将使用attr_accessor方法,但main已经是一个实例化的对象,因此我们不能直接声明新的属性。 这里模块mixin来抢救。

 variable_name = 'foo' variable_value = 'bar' variable_module = Module.new do attr_accessor variable_name.to_sym end include variable_module instance_variable_set("@#{variable_name}", variable_value) p foo # "bar" self.foo = 'bad' p foo # "baz" self.class.ancestors # [Object, #<Module:0x007f86cc073aa0>, Kernel, BasicObject] 

现在你看到main对象被新模块引入了新的属性foo 。 为了进一步检查,您可以运行methods来查看main现在有两个更多的方法foofoo=

为了简化这个操作,我写了metaxa宝石,我强烈建议你去看看。 这是如何使用它的例子。

 require 'metaxa' include Metaxa introduce :foo, with_value: 'foo' puts foo == 'foo' # true puts foo === get(:foo) # true set :foo, 'foobar' puts foo == 'foobar' # true puts foo === get(:foo) # true self.foo = 'foobarbaz' puts foo == 'foobarbaz' # true puts foo === get(:foo) # true