如何动态创建一个局部变量?
我有一个变量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
请注意, eval
和local_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
现在有两个更多的方法foo
和foo=
。
为了简化这个操作,我写了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