在方法定义中使用$ 1,$ 2等全局变量
给出以下两段代码:
def hello(z) "hello".gsub(/(o)/, &z) end z = proc {|m| p $1} hello(z) # prints: nil
def hello z = proc {|m| p $1} "hello".gsub(/(o)/, &z) end hello # prints: "o"
为什么这两段代码的输出不同? 有没有办法从方法定义外部将块传递给gsub
,以便以与在方法定义中给出块的方式相同的方式计算变量$1
, $2
?
为什么输出不同?
ruby中的proc具有词法范围。 这意味着当它找到一个未定义的变量时,它会在上下文中解析proc,而不是调用 proc。 这解释了代码的行为。
您可以看到在regexp之前定义了块,这可能会导致混淆。 这个问题涉及一个魔法ruby变量,它与其他变量的工作方式完全不同。 引用@JörgWMittag
它真的很简单:$ SAFE的行为与你对全局变量的预期不同是因为它不是一个全局变量。 这是一个神奇的独角兽thingamajiggy。
Ruby中有很多神奇的独角兽故事,遗憾的是它们没有很好的文档记录(事实上并没有完全记录),因为替代Ruby实现的开发人员发现了困难的方法。 这些东西都表现得不同,而且(看似)不一致,而且它们共同的唯一两件事就是它们看起来像全局变量,但不像它们那样。
有些人有本地范围。 有些具有线程局部范围。 有些神奇地改变,没有人分配给他们。 有些人对翻译有神奇的意义,并改变语言的行为方式。 有些人还附加了其他奇怪的语义。
如果你真的想要找到$1
和$2
变量的确切工作方式,我认为你会发现唯一的“文档”是rubyspec ,这是rubin人员用硬盘方式完成的ruby规范。 有一个很好的黑客,但要为痛苦做好准备。
有没有办法将$ 1,$ 2变量设置正确的方式从另一个上下文传递块到gsub?
你可以通过以下修改实现你想要的东西(但我打赌你已经知道了)
require 'pp' def hello(z) #z = proc {|m| pp $1} "hello".gsub(/(o)/, &z) end z = proc {|m| pp m} hello(z)
我不知道有办法在飞行中改变过程的范围。 但你真的想这样做吗?
像$1
, $2
这样的东西就像LOCAL VARIABLES一样 ,尽管它领先于$
。 您可以尝试以下代码来certificate这一点:
def foo /(hell)o/ =~ 'hello' $1 end def bar $1 end foo #=> "hell" bar #=> nil
你的问题是因为proc z
是在方法hello
之外定义的,所以z
在main
的上下文中访问$1
,但是gsub
在方法hello
的上下文中设置$1
。
这两个版本是不同的,因为$1
变量是线程本地的和方法本地的。 在第一个示例中, $1
仅存在于hello
方法之外的块中。 在第二个示例中, hello
方法中存在$1
。
无法从方法定义之外将块中的$ 1传递给gsub。
请注意, gsub
将匹配字符串传递给块,因此z = proc { |m| pp m }
z = proc { |m| pp m }
只有在正则表达式只包含整个匹配时才会起作用。 一旦你的正则表达式包含你想要的引用以外的任何东西,你就运气不好。
例如, "hello".gsub(/l(o)/) { |m| m }
"hello".gsub(/l(o)/) { |m| m }
=> hello
,因为整个匹配字符串已传递给块。
然而, "hello".gsub(/l(o)/) { |m| $1 }
"hello".gsub(/l(o)/) { |m| $1 }
=> helo
,因为匹配的l
被块丢弃,我们感兴趣的是捕获的o
。
我的解决方案是match
正则表达式,然后将MatchData
对象传递给块:
require 'pp' def hello(z) string = "hello" regex = /(o)/ m = string.match(regex) string.gsub(regex, z.call(m)) end z = proc { |m| pp m[1] } pp hello(z)
这是一个解决方法(Ruby 2)。 给定的Proc z
行为与String#gsub
的块完全相同。
def hello(z) "hello".match /(o)/ # Sets $1, $2, ... z.binding.tap do |b| b.local_variable_set(:_, $~) b.eval("$~=_") end "hello".gsub(/(o)/, &z) end z = proc {|m| p $1} hello(z) # prints: "o"
在回答 “如何将Regexp.last_match传递给Ruby中的块”(2018年发布)的问题的答案中详细解释了背景知识。