在方法定义中使用$ 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被定义的 ,而不是被调用的 。 这解释了你的代码的行为。
您可以看到该块在regexp之前定义,这可能会导致混淆。 这个问题涉及一个魔术ruby变量,它的工作原理与其他变量完全不同。 引用@JörgWMittag
这很简单,真的:$ SAFE的行为不像你期望从一个全局变量那样的原因是因为它不是一个全局变量。 这是一个神奇的独角兽thingamajiggy。
Ruby中有很多这种神奇的独角兽thingamajiggies,而且不幸的是他们没有很好的文档记录(事实上根本没有记录),因为Ruby实现的替代品开发人员发现了困难的方式。 这些东西的动作都表现出不同的(看起来)不一致,而且他们唯一的两个共同点是他们看起来像全局变量,但不像他们那样表现。
有些具有本地范围。 有些具有线程本地范围。 有些神奇地改变,没有任何人分配给他们。 有些对解释者有着神奇的意义,并改变了语言的行为方式。 有些人会附加其他奇怪的语义。
如果你真的想知道$1
和$2
变量是如何工作的,那么我认为你会发现唯一的“文档”是rubyspec,这是Rubinus人们努力完成的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)
我不知道有什么方法可以即时更改proc的范围。 但你真的想要这样做吗?
这两个版本是不同的,因为$1
变量是线程本地和方法本地。 在第一个例子中, $1
只存在于hello
方法之外的块中。 在第二个例子中, $1
存在于 hello
方法中。
没有办法将方块定义之外的块中的$ 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)
像$1
$2
行为就像本地变量一样,尽管它的领先$
。 你可以尝试下面的代码来证明这一点:
def foo
/(hell)o/ =~ 'hello'
$1
end
def bar
$1
end
foo #=> "hell"
bar #=> nil
你的问题是因为proc z
是在方法hello
外部定义的,所以z
在main
的上下文中访问$1
,但gsub
在方法hello
的上下文中设置$1
。
上一篇: Using $1, $2, etc. global variables inside method definition