Mas*_*ano 5 ruby regex iterator scope yield
有没有办法将最后一个匹配(实际上Regexp.last_match)传递给 Ruby 中的块(迭代器)?
Srring#sub这是一个示例方法,作为演示该问题的一种包装器。它接受标准参数和块:
def newsub(str, *rest, &bloc)\n str.sub(*rest, &bloc)\nend\nRun Code Online (Sandbox Code Playgroud)\n\n它适用于标准的仅参数情况,并且可以占用一个块;然而,像 $1、$2 等位置特殊变量在块内不可用。这里有些例子:
\n\nnewsub("abcd", /ab(c)/, \'\\1\') # => "cd"\nnewsub("abcd", /ab(c)/){|m| $1} # => "d" ($1 == nil)\nnewsub("abcd", /ab(c)/){$1.upcase} # => NoMethodError\nRun Code Online (Sandbox Code Playgroud)\n\n该块的工作方式与String#sub(/..(.)/){$1}我认为的工作方式不同的原因与范围有关;特殊变量 $1、$2 等是局部变量(也是Regexp.last_match)。
有什么办法可以解决这个问题吗?我想让该方法像 $1、$2 等在提供的块中可用的意义上newsub一样工作。String#sub
编辑:根据过去的一些答案,可能没有办法实现这个\xe2\x80\xa6
\n这是根据问题(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
Run Code Online (Sandbox Code Playgroud)
这样,结果如下:
_ = (/(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">
Run Code Online (Sandbox Code Playgroud)
在上面定义的方法中newsub,当给出一个块时,调用者线程中的局部变量 $1 等在块执行后被(重新)设置,这与 一致String#sub。但是,当未给出块时,局部变量 $1 等不会重置,而 in String#sub、 $1 等始终会重置,无论是否给出块。
此外,在此算法中,调用者的局部变量_也会被重置。在 Ruby 的约定中,局部变量_被用作虚拟变量,并且不应读取或引用其值。因此,这应该不会造成任何实际问题。如果该语句local_variable_set(:$~, $~)有效,则不需要临时局部变量。然而,在 Ruby 中却并非如此(至少从版本 2.5.1 开始)。请参阅 Kazuhiro NISHIYAMA 在[ruby-list:50708]中的评论(日语)。
下面是一个简单的例子来强调与此问题相关的 Ruby 规范:
s = "abcd"
/b(c)/ =~ s
p $1 # => "c"
1.times do |i|
p s # => "abcd"
p $1 # => "c"
end
Run Code Online (Sandbox Code Playgroud)
$&、$1、$2等特殊变量(相关的$~( Regexp.last_match)$'等)在局部范围内工作。在 Ruby 中,本地作用域继承父作用域中同名的变量。在上面的示例中,变量s是继承的,因此也是$1。该do块是由yield编辑的1.times,并且该方法1.times无法控制块内的变量,除了块参数(i在上面的示例中;nb,虽然Integer#times不提供任何块参数,但尝试接收一个或多个块参数)一个块将被默默地忽略)。
这意味着yield -sa块的方法无法控制$1块$2中的局部变量(即使它们可能看起来像全局变量)。
现在,让我们分析一下String#sub该块是如何工作的:
'abc'.sub(/.(.)./){ |m| $1 }
Run Code Online (Sandbox Code Playgroud)
在这里,该方法sub首先执行正则表达式匹配,因此$1自动设置局部变量。然后,它们(像 之类的变量$1)在块中继承,因为该块与方法 "sub" 处于相同的作用域中。它们不会从块传递到块,与块参数(它是匹配的字符串,或相当于)sub不同。m$&
因此,如果该方法是在与块不同的作用域中sub定义的,则该方法无法控制块内的局部变量,包括. 不同的范围意味着该方法是使用 Ruby 代码编写和定义的,或者实际上是所有 Ruby 方法,除了一些不是用 Ruby 编写的方法,而是使用与编写 Ruby 解释器相同的语言编写的方法。sub$1sub
Ruby的官方文档(Ver.2.5.1)中的部分解释道String#sub:
在块形式中,当前匹配字符串作为参数传入,$1、$2、$`、$& 和 $' 等变量将被适当设置。
正确的。实际上,能够并且确实设置与正则表达式匹配相关的特殊变量(例如 $1、$2 等)的方法仅限于一些内置方法,包括Regexp#match、Regexp#=~、 Regexp#===、String#=~、String#sub、 String#gsub、String#scan、 Enumerable#all?、 和Enumerable#grep。
提示 1:String#split似乎$~总是重置 nil。
提示2:Regexp#match?并且String#match?不更新$~,因此速度要快得多。
下面是一个小代码片段,用于强调作用域的工作原理:
def sample(str, *rest, &bloc)
str.sub(*rest, &bloc)
$1 # non-nil if matches
end
sample('abc', /(c)/){} # => "c"
p $1 # => nil
Run Code Online (Sandbox Code Playgroud)
这里,$1 在方法中sample()str.sub是在相同的范围内设置的。这意味着该方法sample()将无法(简单地)引用给$1定的块。
我指出Ruby官方文档(Ver.2.5.1)正则表达式一节中的说法
使用
=~带有字符串和正则表达式的运算符,$~全局变量在成功匹配后设置。
相当具有误导性,因为
$~是预定义的局部范围变量(不是全局变量),并且$~无论最后尝试的匹配是否成功,都会被设置(可能为零)。事实上,像$~和 一样的变量$1不是全局变量可能会有点令人困惑。但是,嘿,它们是有用的符号,不是吗?
| 归档时间: |
|
| 查看次数: |
757 次 |
| 最近记录: |