我对Ruby很陌生,到目前为止,弄清楚如何使用"binding" objects对我来说是最大的痛点之一。如果我正确阅读文档,它们几乎完全不透明。要访问绑定对象内的作用域,必须使用绑定的Ruby代码和eval字符串。
也许我只是来自不同学校的纯粹主义者,但一般来说,我对基于字符串的'eval'结构过敏。在给定绑定对象的情况下,是否有任何方法可以安全地执行以下任何操作:
基本上,我想知道哪些是可能的,以及如何完成那些。我想每个人的解决方案都是相当密切相关的,这就是为什么我把所有这些都放在一个问题中。
或者,有没有办法在绑定的上下文中解析已经解析过的代码,类似于Perl的eval BLOCK语法?
答案 0 :(得分:7)
在搜索更多内容时,我找到了至少部分问题的答案:
基于:http://wikis.onestepback.org/index.cgi/Tech/Ruby/RubyBindings.rdoc/style/print
其余的是来自Jim Shubert的有用指针之后的实验。
eval
- local_variables
,instance_variables
和global_variables
来完成。var_name
,new_val
,my_binding
,您可以执行以下所述的操作(语法可能不完善或可以改进,请随时在评论中提出建议。此外,我不能获取代码格式以在列表中工作,还将实现有关如何执行此操作的建议。)这确实涉及使用字符串eval
。但是,没有变量值可以扩展到所涉及的字符串中,因此如果按照描述使用它应该是相当安全的,并且应该能够“传入”复杂的变量值。
另请注意,始终可以eval var_name, my_binding
获取变量的值。请注意,在所有这些用途中,变量的名称对于eval是安全的至关重要,因此理想情况下它根本不应来自任何类型的用户输入。
在给定var_name
,new_val
,my_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对象;通常有更简洁的方法来完成任务。