为什么在ruby救援块中调用的函数无法修改变量?

soc*_*ngh 1 ruby loops exception-handling

我的情况与此代码相当:

i=0
def add_one(i)
  i+=1
  puts "FUNCTION:#{i}"
end

begin
  puts "BEGIN:#{i}"
  raise unless i>5
rescue
  add_one(i)
  puts "RESCUE:#{i}"
  retry
end
Run Code Online (Sandbox Code Playgroud)

当我运行它时,我反复看到这个输出:

BEGIN:0
FUNCTION:1
RESCUE:0
Run Code Online (Sandbox Code Playgroud)

此版本i完美地增加并完成程序:

i=0
begin
  puts "BEGIN:#{i}"
  raise unless i>5
rescue
  i+=1
  puts "RESCUE:#{i}"
  retry
end
Run Code Online (Sandbox Code Playgroud)

为什么会有区别?如何在rescue块中获取函数来实际修改变量?

Hol*_*ust 5

这是因为在您的add_one函数中,您不会操作与i函数外部相同的变量.

让我试着解释一下.在Ruby中,你处理一般可变对象(明显的例外是数字true,falsenil).变量是指向这种对象的指针.多个变量可以指向同一个对象.

a = 123
b = a
Run Code Online (Sandbox Code Playgroud)

现在a,b请参考同一个对象.如果您将一个新对象分配给其中一个a或者b,则它们将再次引用不同的对象,同时仍保留该名称.

你上面有的是局部变量.这些仅在范围内有效,主要是方法的持续时间.如果创建一个新的局部变量(通过为其赋值),它只在方法持续时间内有效,并在离开方法后的某个时候进行垃圾收集.

现在如上所述,Ruby中的大多数对象都是可变的,这意味着您可以在保留变量指针的同时更改它们.一个例子是向数组添加元素:

array = []
array << :foo
Run Code Online (Sandbox Code Playgroud)

现在array变量仍然会引用它初始化的同一个Array对象.但是你已经改变了对象.如果你要为array变量分配一个新的数组

array = [:foo]
Run Code Online (Sandbox Code Playgroud)

它看起来像是同一个对象,但实际上它们是不同的(你可以验证是object_id在每个对象上检查方法.如果它是相同的,你指的是同一个对象)

现在,当i += 1您使用add_one方法时,您正在有效地运行i = i + 1,它将i变量设置为本地方法范围中的新值.您实际上并未更改i变量,而是在本地方法范围中为其分配新值.这意味着i 在外部作用域上命名的变量(即开始/结束块)将引用与方法中的i变量不同的对象add_one.这是因为虽然它们具有相同的名称,但它们具有不同的范围.内部作用域总是掩盖外部作用域,因此虽然在不同作用域中具有相同名称的变量,但它们不会以任何方式干扰(在处理实例或类变量时这会发生变化)

不幸的是,正如我上面所说,数字是不可改变的.你无法改变它们.如果为变量分配新数字,则它是一个新对象.因此,您不能将指向另一个范围中的数字的变量的值更改为更改该值的代码.

为了避免这种情况,您可以从函数返回一个值,并将其显式地分配给i外部作用域中的变量,如下所示

i = 0

def add_one(i)
  i+=1
  puts "FUNCTION:#{i}"
  return i
end

i = add_one(i)
Run Code Online (Sandbox Code Playgroud)

或者您可以使用像这样的对象的实例变量

class Foo
  def initialize
    @i = 0
  end

  def add_one
    @i += 1
  end

  def do_something
    begin
      puts "BEGIN:#{@i}"
      raise unless @i>5
    rescue
      add_one
      puts "RESCUE:#{@i}"
      retry
    end
  end
end

# create a new object and run the instance method
Foo.new.do_something
Run Code Online (Sandbox Code Playgroud)