【问题标题】:Why do functions called in a ruby rescue block fail to modify the variables?为什么在 ruby​​ 救援块中调用的函数无法修改变量?
【发布时间】:2012-06-19 18:00:19
【问题描述】:

我的情况和这段代码差不多:

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

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

BEGIN:0
FUNCTION:1
RESCUE:0

此版本递增i,完美完成程序:

i=0
begin
  puts "BEGIN:#{i}"
  raise unless i>5
rescue
  i+=1
  puts "RESCUE:#{i}"
  retry
end

为什么会有差异?如何在救援块中获取函数来实际修改变量?

【问题讨论】:

    标签: ruby loops exception-handling


    【解决方案1】:

    这是因为在您的 add_one 函数中,您没有在函数外部操作相同的 i 变量。

    让我试着解释一下。在 Ruby 中,您处理的是通常可变的对象(值得注意的例外是数字,truefalsenil)。变量是指向此类对象的指针。多个变量可以指向同一个对象。

    a = 123
    b = a
    

    现在ab 指的是同一个对象。如果您将新对象分配给ab 之一,它们将再次引用不同的对象,同时仍保留名称。

    你上面有的是局部变量。这些仅在范围内有效,主要是在方法的持续时间内。如果您创建一个新的局部变量(通过为其分配一个值),它将仅在方法执行期间有效,并且将在离开方法后的某个时间被垃圾收集。

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

    array = []
    array << :foo
    

    现在array 变量仍将引用它初始化时使用的同一个 Array 对象。但是你已经改变了对象。如果您要为 array 变量分配一个新数组,例如

    array = [:foo]
    

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

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

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

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

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

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

    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
    

    【讨论】:

    • 感谢您的详细解释 - 我现在明白了很多!
    【解决方案2】:

    add_one 中的“i”是本地引用,对参数而言——简而言之,它是一个不同的“i”。

    您需要在正确的范围内使用变量。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-02-26
      • 2023-01-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-07
      相关资源
      最近更新 更多