【问题标题】:Why does += reset the value of the variable it's operating on to nil?为什么 += 将其操作的变量的值重置为零?
【发布时间】:2017-04-29 22:22:05
【问题描述】:

以这段代码为例:

class Thing
  attr_accessor :options, :list

  def initialize
    @list    = []
    @options = { published_at_end: 'NOW', published_at_start: 'NOW-2DAYS' }
  end

  def run
    # If you replace this comment with a debugger, the value of list is nil
    list += _some_method(options)
    return list
  end

  private

  def _some_method(options)
    [options[:published_at_start], 1, 2, 3, 4, options[:published_at_end]]
  end
end

如果您将其复制/粘贴到 irb 中,则运行:

  • t = Thing.new
  • t.run

它会输出这个错误:

NoMethodError: undefined method `+' for nil:NilClass

如果您删除 += 行(只留下 return 行),它会返回 []...所以据我所知,只是 += 的存在将 list 设置为nil。 我还发现它的值是 nil+= 调用之前的行中的 nil 很有趣(请参阅代码示例中的注释)。

或者,如果您将<<flatten 一起使用,您将获得预期的结果:

class Thing
  attr_accessor :options, :list

  def initialize
    @list    = []
    @options = { published_at_end: 'NOW', published_at_start: 'NOW-2DAYS' }
  end

  def run
    list << _some_method(options)
    list.flatten
  end

  private

  def _some_method(options)
    [options[:published_at_start], 1, 2, 3, 4, options[:published_at_end]]
  end
end

如果您将其复制/粘贴到 irb 中,则运行:

  • t = Thing.new
  • t.run

它将输出['NOW-2DAYS', 1, 2, 3, 4, 'NOW']


为什么+=list 的值重置为nil? 此外,在调用+= 之前,它如何将其值设置为nil

半相关/有用的旁注 - 由于performance reasons,我将使用铲子(&lt;&lt;)和flatten,但我仍然对为什么变量重置为nil感兴趣。

【问题讨论】:

    标签: ruby


    【解决方案1】:

    list += [1, 2, 3] 等价于:

    list = list + [1, 2, 3]
    

    因为这是一个assignment,Ruby 会创建一个新的局部变量 list,从而影响您的list 方法。来自文档:

    使用方法分配时,您必须始终有一个接收器。如果你没有接收者,Ruby 会假设你正在分配一个局部变量

    更具体地说,局部变量list 是在解析器遇到list = 时创建的。与未初始化的实例变量和全局变量一样,它的值为nil。因此,尝试评估分配的右侧 list + [1, 2, 3] 失败,因为它相当于:

    nil + [1, 2, 3]
    # NoMethodError: undefined method `+' for nil:NilClass
    

    所以要得到预期的结果,你必须提供一个明确的接收者:

    self.list += [1, 2, 3]
    

    或者直接赋值给实例变量:

    @list += [1, 2, 3]
    

    或者使用修改接收器的方法:

    list.concat [1, 2, 3]
    

    【讨论】:

    • 啊,我明白了 - 所以“Ruby 创建了一个新的局部变量 list”部分发生在包含 += 的行之前的某个时间......这就是为什么如果我添加 puts list 只是在+= 行之前,它输出nil ...
    • @JamesChevalier 它发生在解析器级别。解析器看到一个赋值并将list 标记为一个局部变量。在该行之前,list 应该解析为方法并返回 []。不知道为什么你得到nil,也许这条线已经被评估了。
    • 对于attr_reader :list,我会理解这种行为,因为不会定义self.list=。但是attr_accessor 呢?顺便说一句,当a 未定义时,这似乎与a=a 为零有关。
    • @EricDuminil 这就是 Ruby 的工作方式——赋值方法总是需要一个明确的接收者。
    • “赋值方法总是需要一个明确的接收者”——或者一个明确的方法调用method(:list=).call(42) :)
    【解决方案2】:

    我相信list += _some_method(options) 正在内部编译成list = list + _some_method(options)。尽管存在list 方法,Ruby 将list = :anything 解释为定义了一个名为list 的新局部变量,覆盖了方法调用范围内list 的解释。当 Ruby 错误地跳到“局部变量”结论时,必须用self. 告知。这就是切换到 self.list += _some_method(options) 有效的原因。

    class Thing
        def foo; 'BAR'; end
        def baz; foo = 'ODD'; foo; end
    end
    
    Thing.new.foo #=> "BAR" 
    Thing.new.baz #=> "ODD" 
    

    但我同意使用list &lt;&lt; 后跟flatten(1) 仍然更有效。

    【讨论】:

    • list.push(*_some_method(options)) 可能更有效。 .concat 甚至应该是最重要的:)
    • 是的,我运行了一个基准测试,比较了 3 和 push/concat 的表现略优于 shovel/flatten - gist.github.com/JamesChevalier/1a6fabde0b56c08691aecb48a37c7174
    • 当我将一个列表附加到另一个列表时,我使用list +=,例如:numbers += [3,4,5]numbers 添加三个新的数字元素,而numbers &lt;&lt; [3,4,5] 添加一个数组元素。似乎list.concatlist += 的就地版本,如果这是您真正需要的。事实上,我敢打赌list + x 只是实现为list.dup.concat(x)
    • “我打赌 list + x 只是实现为 list.dup.concat(x)Array#+ 创建一个具有最终大小的新数组,memcpys 两个数组都放入其中.这比复制和调整大小更有效。
    • 对调试器在list += 语句之前返回nillist 感到好奇。我修改了我的测试代码如下:
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-04-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多