【问题标题】:Dynamically set local variables in Ruby [duplicate]在Ruby中动态设置局部变量[重复]
【发布时间】:2011-06-25 05:19:26
【问题描述】:

我对在 Ruby 中动态设置局部变量很感兴趣。不创建方法、常量或实例变量。

比如:

args[:a] = 1
args.each_pair do |k,v|
  Object.make_instance_var k,v
end
puts a
> 1

我特别想要局部变量,因为有问题的方法存在于模型中,我不想污染全局或对象空间。

【问题讨论】:

  • 是否有任何特殊原因需要局部变量而不是简单地访问 args 中的对?
  • 你的意思是在方法 X 中有一堆变量名和值,然后在方法 Y 中用作方法 Y 的局部变量?另外,这里有一个来自 matz 的有用评论:ruby-forum.com/topic/155673#685906
  • 基本上我有一组参数模糊不清的函数,传入会清晰很多:present_value(:n=>6,:m=>4..etc),然后在函数中自动将它们用作局部变量,而不是通过重新定义它们或以散列形式使用它们来重复自己。根据您的评论 Zabba 我查看了:github.com/maca/arguments ...但由于它显然不再处于积极开发状态,因此无法使其在 1.9.2 中可靠地工作。
  • 呵呵,1.9.2 不行了,我们失去了从活体中提取树的功能。你可以用它做一些疯狂的事情。我是作者
  • Ruby 2.2.2 可能会添加通过 binding.local_variables 添加的功能

标签: ruby metaprogramming local-variables


【解决方案1】:

作为对未来读者的附加信息,从 ruby​​ 2.1.0 开始,您可以使用 binding.local_variable_get 和 binding.local_variable_set:

def foo
  a = 1
  b = binding
  b.local_variable_set(:a, 2) # set existing local variable `a'
  b.local_variable_set(:c, 3) # create new local variable `c'
                              # `c' exists only in binding.
  b.local_variable_get(:a) #=> 2
  b.local_variable_get(:c) #=> 3
  p a #=> 2
  p c #=> NameError
end

如the doc 所述,与

binding.eval("#{symbol} = #{obj}")
binding.eval("#{symbol}")

【讨论】:

  • 您的示例无法按原样运行,因为b.local_variable_set(:b, 3) 会覆盖您之前存储在b 中的绑定。将绑定存储在不同的变量中可以解决问题。
  • @Kelvin 谢谢,我修好了
【解决方案2】:

我建议您使用哈希(但请继续阅读以了解其他替代方案)。

为什么?

允许任意命名参数会导致代码极其不稳定。

假设你有一个方法foo,你想接受这些理论上的命名参数。

场景:

  1. 被调用的方法(foo)需要调用一个不带参数的私有方法(我们称之为bar)。如果您将参数传递给foo,并希望将其存储在局部变量bar 中,它将屏蔽bar 方法。解决方法是在调用bar 时使用显式括号。

  2. 假设foo 的代码分配了一个局部变量。但随后调用者决定传入一个与该局部变量同名的 arg。分配将破坏参数。

基本上,方法的调用者永远不能改变方法的逻辑。

替代品

另一个中间立场涉及OpenStruct。它比使用散列更少打字。

require 'ostruct'
os = OpenStruct.new(:a => 1, :b => 2)
os.a  # => 1
os.a = 2  # settable
os.foo  # => nil

请注意,OpenStruct 允许您访问不存在的成员 - 它会返回 nil。如果您想要更严格的版本,请改用Struct。

这会创建一个匿名类,然后实例化该类。

h = {:a=>1, :b=>2}
obj = Struct.new(* h.keys).new(* h.values)
obj.a  # => 1
obj.a = 2  # settable
obj.foo  # NoMethodError

【讨论】:

    【解决方案3】:

    有趣的是,您可以更改局部变量,但不能设置它:

    def test
      x=3
      eval('x=7;')
      puts x
    end
    

    测试 => 7

    def test
      eval('x=7;')
      puts x
    end
    

    测试 => NameError: undefined local variable or method `x' for main:Object

    这是 Dorkus Prime 的代码有效的唯一原因。

    【讨论】:

      【解决方案4】:

      因为你不想要常量

      args = {}
      args[:a] = 1
      args[:b] = 2
      
      args.each_pair{|k,v|eval "@#{k}=#{v};"}
      
      puts @b
      

      2

      您可能会发现这种方法很有趣(在正确的上下文中评估变量)

      fn="b*b"
      vars=""
      args.each_pair{|k,v|vars+="#{k}=#{v};"}
      eval vars + fn
      

      4

      【讨论】:

      • 我没有反对,但 OP 说实例变量是不可接受的。这是合理的,因为这会改变对象的状态。
      【解决方案5】:

      这里的问题是 each_pair 里面的块有不同的作用域。其中分配的任何局部变量将只能在其中访问。例如,这个:

      args = {}
      args[:a] = 1
      args[:b] = 2
      
      args.each_pair do |k,v|
        key = k.to_s
        eval('key = v')
        eval('puts key')
      end
      
      puts a
      

      产生这个:

      1
      2
      undefined local variable or method `a' for main:Object (NameError)
      

      为了解决这个问题,您可以创建一个本地散列,将键分配给该散列,然后在那里访问它们,如下所示:

      args = {}
      args[:a] = 1
      args[:b] = 2
      
      localHash = {}
      args.each_pair do |k,v|
        key = k.to_s
        localHash[key] = v
      end
      
      puts localHash['a']
      puts localHash['b']
      

      当然,在这个例子中,它只是复制原始散列和字符串作为键。不过,我假设实际用例更复杂。

      【讨论】:

      • 你是对的。本质上,我想要一种轻松的方式来传递命名参数,然后在方法中使用它们。太糟糕了 eval 技巧没有得到它,我想任何选项无论如何都会有悲惨的表现。
      • key = k.to_s 后跟 eval('key = v') 没有你期望的效果,key = 1234 有同样的效果
      • eval 并不理想,因为并非所有内容都可以轻松序列化为字符串
      猜你喜欢
      • 2011-04-21
      • 2018-06-04
      • 2023-03-12
      • 1970-01-01
      • 1970-01-01
      • 2018-11-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多