我正在处理一个基于记录的文本文件:所以我在寻找一个起始字符串,它构成了一条记录的开始:没有记录结束标记,所以我使用下一个记录的开始来分隔最后一个记录。
所以我已经建立了一个简单的程序来做这个,但是我看到了一些让我吃惊的事情:看起来Ruby好像忘记了局部变量的存在--或者我发现了一个编程错误?[尽管我不认为我有:如果我在循环之前定义变量'message',我看不到错误]。
下面是一个简化的示例,其中包含示例输入数据和注解中的错误消息:
flag=false
# message=nil # this is will prevent the issue.
while line=gets do
if line =~/hello/ then
if flag==true then
puts "#{message}"
end
message=StringIO.new(line);
puts message
flag=true
else
message << line
end
end
# Input File example:
# hello this is a record
# this is also part of the same record
# hello this is a new record
# this is still record 2
# hello this is record 3 etc etc
#
# Error when running: [nb, first iteration is fine]
# <StringIO:0x2e845ac>
# hello
# test.rb:5: undefined local variable or method `message' for main:Object (NameError)
#
6条答案
按热度按时间zzzyeukh1#
从Ruby编程语言:
(来源:google.com)
块和变量范围
块定义新的变量范围:在一个块内创建的变量只存在于该块内,在块外是未定义的。2但是要小心;一个方法中的局部变量对该方法中的任何块都是可用的。因此,如果一个块给一个已经在块外定义的变量赋值,这并不是创建一个新的块局部变量,而是给已经存在的变量赋值。有时候,这正是我们想要的行为:
然而,有时候,我们不想改变封闭作用域中的变量,但是我们无意中这样做了。这个问题对于Ruby 1.8中的块参数来说尤其值得关注。在Ruby 1.8中,如果一个块参数共享一个现有变量的名称,那么调用该块时只需给该现有变量赋值,而不是创建一个新的块局部变量。例如,下面的代码是有问题的,因为它使用相同的标识符i作为两个嵌套块的块参数:
Ruby 1.9是不同的:块参数对于它们的块总是局部的,并且块的调用从不给已有的变量赋值。2如果Ruby 1.9是用-w标志调用的,那么如果块参数和已有的变量同名,它会警告你。3这有助于你避免编写在1.8和1.9中运行不同的代码。
Ruby 1.9在另一个重要方面也有所不同。块语法已经被扩展,允许你声明保证是局部变量的块局部变量,即使一个同名的变量已经存在于封闭的作用域中。要做到这一点,在块参数列表后面跟一个分号和一个逗号分隔的块局部变量列表。下面是一个例子:
在此代码中,x是块参数:当yield调用该块时,它会得到一个值。y是一个块局部变量。它不会从yield调用中接收任何值,但是在块实际给它赋值之前,它的值为nil。声明这些块局部变量的目的是保证你不会无意中破坏一些现有变量的值。(例如,如果块被从一个方法剪切粘贴到另一个方法,就可能发生这种情况。)如果使用-w选项调用Ruby 1.9,那么如果块局部变量隐藏了现有变量,它就会发出警告。
当然,块可以有多个参数和多个局部变量。下面是一个有两个参数和三个局部变量的块:
vbopmzt12#
与其他一些答案相反,
while
循环实际上并没有创建新的作用域,您看到的问题更加微妙。为了帮助显示对比,传递给方法调用 * DO * 的块创建了一个新的作用域,这样块中新赋值的局部变量在块退出后就消失了:
但是
while
循环(就像你的例子一样)是不同的,因为在循环中定义的变量将持续存在:在本例中出现错误的原因是使用
message
的行:出现在 * 指定 *
message
的任何代码之前。如果
a
没有预先定义,那么这段代码也会引发一个错误:所谓的"问题"--即在一个作用域中的局部变量可见性--是由于ruby的解析器造成的。因为我们只考虑一个作用域,作用域规则与这个问题**没有任何关系。在解析阶段,解析器决定局部变量在哪个源代码位置可见,并且这个可见性在执行过程中 * 不会 * 改变。
当确定一个局部变量是否在代码中的任何一点被定义(例如
defined?
返回true)时,解析器会检查当前作用域以查看是否有任何代码在此之前赋值过它,即使该代码从未运行过(解析器在解析阶段不知道什么运行过或没有运行过)。在上面一行,或在同一行并在左手边。注意,下面的内容只适用于局部变量,而不适用于方法。(确定一个方法是否在作用域中定义更为复杂,因为它涉及到搜索包含的模块和祖先类。)
查看局部变量行为的一个具体方法是在文本编辑器中打开文件。再假设通过重复按左箭头键,可以在整个文件中向后移动光标。现在假设您想知道
message
的某个用法是否会提高NameError
。要做到这一点,请将光标定位在使用message
的位置。然后继续按左箭头键,直到您:1.到达当前作用域的开始(你必须理解ruby的作用域规则,以便知道何时发生这种情况)
1.分配
message
的到达代码如果你在到达作用域边界之前已经到达了一个赋值,那就意味着你对
message
的使用不会引发NameError
;如果你没有到达任何赋值,那么你的使用将引发NameError
。如果代码中出现了变量赋值但没有运行,则变量将被初始化为
nil
:下面是一个小测试用例,演示了上述行为在while循环中发生时的奇怪之处,受影响的变量是
dest_arr
。其输出:
要点如下:
dest_arr
被初始化为[0]
。n
为1
时),我们需要密切注意:dest_arr
未定义!else
的情况时,dest_arr
再次可见,因为解释器看到它在上面被分配了两行。dest_arr
只在循环开始时是 * hidden * 的;其价值永远不会丧失(如最终含量为[0, 1]
所证明的)。这也解释了为什么在
while
循环之前给你的局部变量赋值可以解决这个问题。它只需要出现在源代码中。通过在
f1
的主体前放置一个f2
赋值来解决这个问题。记住,赋值实际上并不需要执行!如果你有一个和方法同名的局部变量,事情会变得非常棘手:
输出:
作用域中的局部变量赋值将"屏蔽"/"遮蔽"同名的方法调用。(您仍然可以通过使用显式括号或显式接收器来调用该方法。)因此,这与前面的
while
循环测试类似,不同之处在于,它不是在赋值代码上方变为未定义,方法变成"unmasked "/" unshaded",这样方法就可以不带括号调用了。但是赋值之后的任何代码都可以看到局部变量。while
或for
循环体中,或者任何会导致执行在作用域中跳转的语句(调用lambdas或Continuation#call
也可以做到这一点)。bjp0bcyl3#
我认为这是因为消息是在循环内部定义的。在循环迭代结束时,“消息”超出了作用域。在循环外部定义“消息”可以防止变量在每次循环迭代结束时超出作用域。所以我认为你的答案是正确的。
您可以在每次循环迭代开始时输出message的值,以测试我的建议是否正确。
qmelpv7a4#
为什么你认为这是一个bug?解释器告诉你,当特定的代码段执行时,消息可能是未定义的。
ar5n3qh55#
我不知道你有什么好惊讶的在第5行(假设
message = nil
行不存在),您可能使用了一个解释器从未听说过的变量,解释器会说“什么是message
?它不是我知道的方法,它不是我知道的变量,它不是关键字......”然后您会得到一条错误消息。下面是一个简单的例子来说明我的意思:
其中:
另外,如果你想为
message
做好准备,我会把它初始化为message = ''
,这样它就是一个字符串(而不是nil
),否则,如果你的第一行 * 不 * 匹配hello,你最终会尝试把line
添加到nil
--这会给你带来这样的错误:umuewwlo6#
你可以简单地这样做: