30

我有一个变量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.
4

4 回答 4

39

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

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

但是,它们可以在 eval 代码本身中使用:

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[:my_var] = :foo

请注意,两者evallocal_variable_set 允许重新分配现有的局部变量:

foo = nil
eval 'foo = "bar"'
foo  #=> "bar"
binding.local_variable_set :foo, 'baz'
foo  #=> "baz"
于 2013-08-31T21:26:38.747 回答
5

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

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

binding这里有趣的是,每次调用都会给你一个新的绑定。因此,您需要获取您感兴趣的绑定的句柄,然后在设置所需的变量后在其上下文中进行 eval。

这有什么用?例如,我想评估 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_instance_methodmy_object同样,您可以SomeOtherClass.new使用此绑定而不是MyRootModule::SomeOtherClass.new. 因此,在创建绑定时,有时需要考虑的不仅仅是局部变量。高温高压

于 2015-12-28T21:36:35.397 回答
3

其他人写的确实不能在本地上下文中动态声明真正的变量。但是,您可以使用对象属性实现类似的功能,并且由于在 Ruby 世界中一切都是对象(甚至是主上下文),您可以轻松地使用新属性扩展这些对象。当然,这个操作可以动态完成。让我们研究一下这种方法。

首先,让我们看一下主要作用域irb

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

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

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 gem,我强烈建议你去看看。这是如何使用它的示例。

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     
于 2016-10-10T17:51:35.267 回答
2

尽管正如其他人指出的那样,您不能在 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没有+方法,因此抛出错误。

因此,虽然此方法对于模拟只读局部变量非常有用,但当您尝试在块内重新分配变量时,它并不总是有效。

于 2015-08-14T16:36:04.223 回答