'eval'是与Ruby中绑定对象进行交互的唯一方法吗?

时间:2010-06-22 01:33:33

标签: ruby eval scope

我对Ruby很陌生,到目前为止,弄清楚如何使用"binding" objects对我来说是最大的痛点之一。如果我正确阅读文档,它们几乎完全不透明。要访问绑定对象内的作用域,必须使用绑定的Ruby代码和eval字符串。

也许我只是来自不同学校的纯粹主义者,但一般来说,我对基于字符串的'eval'结构过敏。在给定绑定对象的情况下,是否有任何方法可以安全地执行以下任何操作:

  1. 在绑定表示的上下文中列出范围内的标识符,或检索内容的哈希值。
  2. 将绑定中的局部变量的值设置为等于外部上下文中某个局部变量的值。理想情况下,即使值是对象引用,文件句柄或其他复杂实体,这通常也可以正常工作。
  3. (扩展名2 :)给定哈希,在每个条目的绑定中设置本地。
  4. 更好的是,给定哈希构建与基本语言构造的绑定以及范围内哈希中的名称。
  5. 基本上,我想知道哪些是可能的,以及如何完成那些。我想每个人的解决方案都是相当密切相关的,这就是为什么我把所有这些都放在一个问题中。

    或者,有没有办法在绑定的上下文中解析已经解析过的代码,类似于Perl的eval BLOCK语法?

4 个答案:

答案 0 :(得分:7)

在搜索更多内容时,我找到了至少部分问题的答案:

基于:http://wikis.onestepback.org/index.cgi/Tech/Ruby/RubyBindings.rdoc/style/print

其余的是来自Jim Shubert的有用指针之后的实验。

  1. 这可以通过绑定中的eval - local_variablesinstance_variablesglobal_variables来完成。
  2. 根据var_namenew_valmy_binding,您可以执行以下所述的操作(语法可能不完善或可以改进,请随时在评论中提出建议。此外,我不能获取代码格式以在列表中工作,还将实现有关如何执行此操作的建议。)
  3. 你可以直接拿(2)并循环哈希来做到这一点。
  4. 请参阅下面的第二个代码块。我的想法是从TOPLEVEL_BINDING开始,我认为通常只包括全局变量。
  5. 这确实涉及使用字符串eval。但是,没有变量值可以扩展到所涉及的字符串中,因此如果按照描述使用它应该是相当安全的,并且应该能够“传入”复杂的变量值。

    另请注意,始终可以eval var_name, my_binding获取变量的值。请注意,在所有这些用途中,变量的名称对于eval是安全的至关重要,因此理想情况下它根本不应来自任何类型的用户输入。

    在给定var_namenew_valmy_binding的约束内设置变量:

    # the assignment to nil in the eval coerces the variable into existence at the outer scope
    setter = eval "#{var_name} = nil; lambda { |v| #{var_name} = v }", my_binding
    setter.call(new_val)
    

    构建“定制”绑定:

    my_binding = eval "lambda { binding }", TOPLEVEL_BINDING # build a nearly-empty binding
    # set_in_binding is based on the above snippet
    vars_to_include.each { |var_name, new_val| set_in_binding(var_name, new_val, my_binding) }
    

答案 1 :(得分:3)

Walter,您应该能够直接与绑定进行交互。我之前没有使用绑定工作,但我在irb中运行了一些东西:

jim@linux-g64g:~> irb
irb(main):001:0> eval "self", TOPLEVEL_BINDING
=> main
irb(main):002:0> eval "instance_variables", TOPLEVEL_BINDING
=> []
irb(main):003:0> eval "methods", TOPLEVEL_BINDING
=> ["irb_kill", "inspect", "chws", "install_alias_method", ....

我也有Metaprogramming Ruby,它没有谈论很多关于绑定的内容。但是,如果你选择了这个,那么在第144页的末尾就是

  

从某种意义上说,你可以看到Binding   对象作为“更纯粹”的闭包形式   比块,因为这些对象   包含范围但不包含   代码。

而且,在相反的页面上,它建议修改irb的代码(删除eval调用的最后两个args)以查看它如何使用绑定:

// ctwc / irb / workspace.rb
eval(statements,@ binding)#,file,line)

并且......我打算建议传递lambda,但是我看到你刚刚回答了这个问题,所以我会留下一些修补作为进一步研究的建议。

答案 2 :(得分:1)

你能解释一下你究竟想做什么吗?请提供一些代码,说明您希望它如何工作。可能有更好,更安全的方式来实现你想要的东西。

我打算猜测你的典型用例。 给出一个哈希: {:a => 11,:b => 22}

您需要一个最小的,相对隔离的执行环境,您可以在其中访问哈希值作为局部变量。 (我不确定为什么你需要他们成为当地人,除非你正在编写DSL,或者你已经编写过的代码,你不想适应访问哈希。)< / p>

这是我的尝试。为简单起见,我假设您只使用符号作为哈希键。

class MyBinding
  def initialize(varhash); @vars=varhash; end
  def method_missing(methname, *args)
    meth_s = methname.to_s
    if meth_s =~ /=\z/
      @vars[meth_s.sub(/=\z/, '').to_sym] = args.first
    else
      @vars[methname]
    end
  end
  def eval(&block)
    instance_eval &block
  end
end

样本用法:

hash = {:a => 11, :b => 22}
mb = MyBinding.new hash
puts mb.eval { a + b }
# setting values is not as natural:
mb.eval { self.a = 33 }
puts mb.eval { a + b }

一些警告:

1)如果变量不存在,我没有引发NameError,但是一个简单的替换修复了:

def initialize(varhash)
  @vars = Hash.new {|h,k| raise NameError, "undefined local variable or method `#{k}'" }
  @vars.update(varhash)
end

2)正常的作用域规则是这样的:如果存在一个名称与方法相同的本地,则本地优先,除非您明确地执行方法调用,如a()。上面的类有相反的行为;该方法优先。要获得“正常”行为,您需要隐藏所有(或大多数)标准方法,例如#object_id。 ActiveSupport为此提供了一个BlankSlate类;它只保留3种方法:

 __send__, instance_eval, __id__

。要使用它,只需使MyBinding继承自BlankSlate。 除了这三种方法之外,您还无法拥有名为“eval”或“method_missing”的本地人。

3)设置本地不是很自然,因为它需要“self”来接收方法调用。不确定是否有解决方法。

4)eval块可能会混乱@vars哈希。

5)如果你在调用mb.eval的范围内有一个真正的局部变量,并且与其中一个散列键同名,那么真正的本地优先级...这可能是最大的缺点因为微妙虫子可以进入。

在意识到我的技术的缺点之后,我建议直接使用哈希来保留一组变量,除非我看到其他原因。但是如果你仍然想使用本机eval,你可以通过使用Regexps来避免代码注入来提高安全性,并通过使用Proc来“e本地”设置$ SAFE更高的eval调用,如下所示:

proc { $SAFE = 1; eval "do_some_stuff" }.call  # returns the value of eval call

答案 3 :(得分:0)

以下是基于哈希的解决方案的一些代码。

class ScopedHash
  def initialize(varhash)
    # You can use an OpenStruct instead of a Hash, but the you lose the NameError feature.
    # OpenStructs also don't have the ability to list their members unless you call a protected method
    @vars = Hash.new {|h,k| raise NameError, "undefined local variable or method `#{k}'" }
    @vars.update(varhash)
  end
  def eval
    yield @vars
  end
end

if __FILE__ == $0
  # sample usage
  hash = {:a => 11, :b => 22}
  sh = ScopedHash.new hash
  puts sh.eval {|v| v[:a] + v[:b] }
  sh.eval {|v| v[:a] = 33 }
  puts sh.eval {|v| v[:a] + v[:b] }
  sh.eval{|v| v[:c] }  # raises NameError
end

因此,代替使用本地人,你只需要访问屈服的哈希。我认为很少有人会被迫操纵Binding对象;通常有更简洁的方法来完成任务。