如何将 Regexp.last_match 传递给 Ruby 中的块

2024-02-05

有什么办法可以通过最后一场比赛(几乎Regexp.last_match) 到 Ruby 中的块(迭代器)?

这是一个示例方法,作为一种包装器Srring#sub来演示问题。它接受标准参数和块:

def newsub(str, *rest, &bloc)
  str.sub(*rest, &bloc)
end

它适用于标准的仅参数情况,并且可以占用一个块;然而,像 $1、$2 等位置特殊变量在块内不可用。这里有些例子:

newsub("abcd", /ab(c)/, '\1')        # => "cd"
newsub("abcd", /ab(c)/){|m| $1}      # => "d"  ($1 == nil)
newsub("abcd", /ab(c)/){$1.upcase}   # => NoMethodError

该块的工作方式与以下方式不同的原因String#sub(/..(.)/){$1}我认为与范围有关吗?特殊变量 $1、$2 等是局部变量(也是如此)Regexp.last_match).

有什么办法可以解决这个问题吗?我想做这个方法newsub工作就像String#sub从某种意义上说,$1、$2 等在提供的块中是可用的。

编辑:根据一些过去的答案 https://stackoverflow.com/questions/18550434/using-1-2-etc-global-variables-inside-method-definition,可能没有办法实现这一点......


这是根据问题(Ruby 2)的一种方法。它并不漂亮,各方面也不是 100% 完美,但可以完成工作。

def newsub(str, *rest, &bloc)
  str =~ rest[0]  # => ArgumentError if rest[0].nil?
  bloc.binding.tap do |b|
    b.local_variable_set(:_, $~)
    b.eval("$~=_")
  end if bloc
  str.sub(*rest, &bloc)
end

这样,结果如下:

_ = (/(xyz)/ =~ 'xyz')
p $1  # => "xyz"
p _   # => 0

p newsub("abcd", /ab(c)/, '\1')        # => "cd"
p $1  # => "xyz"
p _   # => 0

p newsub("abcd", /ab(c)/){|m| $1}      # => "cd"
p $1  # => "c"
p _                 # => #<MatchData "abc" 1:"c">

v, _ = $1, newsub("efg", /ef(g)/){$1.upcase}
p [v, _]  # => ["c", "G"]
p $1  # => "g"
p Regexp.last_match # => #<MatchData "efg" 1:"g">

深入分析

在上面定义的方法中newsub,当给出一个块时,调用者线程中的局部变量 $1 等在块执行后被(重新)设置,这与String#sub。然而,当没有给出块时,局部变量 $1 等是not重置,而在String#sub、 $1 等总是重置,无论是否给出块。

另外,调用者的局部变量_在此算法中被重置。在 Ruby 的约定中,局部变量_用作虚拟变量,不应读取或引用其值。因此,这应该不会造成任何实际问题。如果声明local_variable_set(:$~, $~)是有效的,不需要临时局部变量。然而,在 Ruby 中却并非如此(至少从版本 2.5.1 开始)。请参阅 Kazuhiro NISHIYAMA 的评论(日语)[红宝石列表:50708] http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/50708.

一般背景(Ruby 的规范)解释

下面是一个简单的例子来强调与此问题相关的 Ruby 规范:

s = "abcd"
/b(c)/ =~ s
p $1     # => "c"
1.times do |i|
  p s    # => "abcd"
  p $1   # => "c"
end

特殊变量为$&, $1, $2等(相关,$~ (Regexp.last_match), $'和类似的) 在本地范围内工作。在 Ruby 中,本地作用域继承父作用域中同名的变量。 在上面的例子中,变量s is 遗传,也是如此$1. The do块是yield-ed by 1.times,以及方法1.times除了块参数之外,无法控制块内的变量(i在上面的例子中;n.b., 虽然Integer#times不提供任何块参数,尝试接收块中的参数将被默默忽略)。

这意味着一种方法yield-s 块无法控制$1, $2等在块中,它们是局部变量(即使它们可能看起来像全局变量)。

字符串#sub 的情况

现在我们就来分析一下如何String#sub与块作品:

'abc'.sub(/.(.)./){ |m| $1 }

这里,方法sub首先执行正则表达式匹配,因此局部变量如$1会自动设置。然后,他们(变量如$1) 在块中继承,因为这个块与方法“sub”处于相同的范围内。他们是不及格 from sub到块,与块参数不同m(这是一个匹配的字符串,或相当于$&).

因此,如果该方法sub被定义在一个范围不同从块中,sub方法无法控制块内的局部变量,包括$1. A 范围不同是指以下情况:sub方法是使用 Ruby 代码编写和定义的,或者实际上,所有 Ruby 方法(除了一些不是用 Ruby 编写但使用与编写 Ruby 解释器相同的语言编写的方法)。

Ruby's 官方文档(Ver.2.5.1) https://ruby-doc.org/core-2.5.1/String.html#method-i-sub在 部分中进行了解释String#sub:

在块形式中,当前匹配字符串作为参数传入,$1、$2、$`、$& 和 $' 等变量将被适当设置。

正确的。在实践中,可以并且确实设置与正则表达式匹配相关的特殊变量(例如 $1、$2 等)的方法仅限于某些内置方法,包括Regexp#match, Regexp#=~, Regexp#===,String#=~, String#sub, String#gsub, String#scan, Enumerable#all?, and Enumerable#grep.
Tip 1: String#split似乎重置$~始终为零。
Tip 2: Regexp#match? and String#match?不更新$~因此速度要快得多。

下面是一个小代码片段,用于强调作用域的工作原理:

def sample(str, *rest, &bloc)
  str.sub(*rest, &bloc)
  $1    # non-nil if matches
end

sample('abc', /(c)/){}  # => "c"
p $1    # => nil

Here, $1 在方法示例()中由设置str.sub在同一范围内。这意味着该方法sample()将无法(简单地)参考$1在给它的块中。

我指出了正则表达式部分 https://ruby-doc.org/core-2.5.1/doc/regexp_rdoc.html#class-Regexp-label-3D~+operatorRuby官方文档(Ver.2.5.1)

Using =~带有字符串和正则表达式的运算符$~全局变量在成功匹配后设置。

相当具有误导性,因为

  1. $~ is a 预定义局部范围多变的 (not全局变量),以及
  2. $~无论最后一次尝试的匹配是否成功,都会被设置(可能为零)。

事实上变量就像$~ and $1不是全局变量可能会有点令人困惑。但是,嘿,它们是有用的符号,不是吗?

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何将 Regexp.last_match 传递给 Ruby 中的块 的相关文章

随机推荐