【问题标题】:Confusion with the assignment operation inside a falsy `if` block [duplicate]与虚假的`if`块中的赋值操作混淆[重复]
【发布时间】:2013-03-03 07:48:31
【问题描述】:

我在if 块内玩赋值操作,发现了下面的结果,这让我很惊讶:

C:\>irb --simple-prompt
if false
x = 10
end
#=> nil
p x
nil
x.object_id
#=> 4
#=> nil
p y
NameError: undefined local variable or method `y' for main:Object
        from (irb):5
        from C:/Ruby193/bin/irb:12:in `<main>'

在上面的代码中,您可以看到 x 局部变量已创建,尽管它仅在虚假的 if 块中分配。我试图用p x 查看x 的内容,这迫使我相信分配没有完成,但x 变量存在。 x.object_id也证明是这样的。

现在我的问题是 x 局部变量是如何创建的,即使 if 块入口点被有意永久关闭?

我希望p x 的输出与p y 的输出相似。但相反,我从p x 得到了一个令人惊讶的答案。

有人可以向我解释一下这个概念是如何工作的吗?

编辑

不,这是另一个测试。只有local 变量不是这种情况。 instanceclass 变量也发生了同样的情况。见下文:

class Foo
  def show
    @X = 10 if false
    p @X,"hi",@X.object_id
  end
end
#=> nil
Foo.new.show
nil
"hi"
4
#=> [nil, "hi", 4]

class Foo
  def self.show
    @@X = 10 if false
    p @@X,"hi",@@X.object_id
  end
end
#=> nil
Foo.show
nil
"hi"
4
#=> [nil, "hi", 4]

成功案例:

class Foo
  def self.show
    @@X = 10 if true
    p @@X,"hi",@@X.object_id
  end
end
#=> nil
Foo.show
10
"hi"
4
#=> [10, "hi", 4]

【问题讨论】:

    标签: ruby if-statement nameerror


    【解决方案1】:

    在 Ruby 中,局部变量由解析器在第一次遇到赋值时定义,然后从那时起就在范围内。

    这里有一个小示范:

    foo # NameError: undefined local variable or method `foo' for main:Object
    
    if false
      foo = 42
    end
    
    foo # => nil
    

    如您所见,局部变量 确实 存在于第 7 行,即使第 4 行的赋值从未执行。然而,它被解析,这就是局部变量foo 存在的原因。但由于从未执行过赋值,因此变量未初始化,因此计算结果为 nil 而不是 42

    在 Ruby 中,大多数未初始化甚至不存在的变量的计算结果为 nil。对于局部变量、实例变量和全局变量都是如此:

    defined? foo       #=> nil
    local_variables    #=> []
    if false
      foo = 42
    end
    defined? foo       #=> 'local-variable'
    local_variables    #=> [:foo]
    foo                #=> nil
    foo.nil?           #=> true
    
    defined? @bar      #=> nil
    instance_variables #=> []
    @bar               #=> nil
    @bar.nil?          #=> true
    # warning: instance variable @bar not initialized
    
    defined? $baz      #=> nil
    $baz               #=> nil
    # warning: global variable `$baz' not initialized
    $baz.nil?          #=> true
    # warning: global variable `$baz' not initialized
    

    但是,对于类层次结构变量和常量,情况并非如此:

    defined? @@wah     #=> nil
    @@wah
    # NameError: uninitialized class variable @@wah in Object
    
    defined? QUUX      #=> nil
    QUUX
    # NameError: uninitialized constant Object::QUUX
    

    这是一条红鲱鱼:

    defined? fnord     #=> nil
    local_variables    #=> []
    fnord
    # NameError: undefined local variable or method `fnord' for main:Object
    

    您在此处收到错误的原因是不是未初始化的局部变量不会评估为nil,而是fnord 不明确:它可能是两者 发送到默认接收者的无参数消息(即等同于self.fnord()访问局部变量fnord

    为了消除歧义,您需要添加一个接收者或一个参数列表(即使是空的)来告诉 Ruby 这是一个消息发送:

    self.fnord
    # NoMethodError: undefined method `fnord' for main:Object
    fnord()
    # NoMethodError: undefined method `fnord' for main:Object
    

    或确保解析器不是评估器)在使用前解析(执行)一个赋值,告诉Ruby它是一个局部变量:

    if false
      fnord = 42
    end
    fnord              #=> nil
    

    当然,nil 是一个对象(它是类NilClass 的唯一实例),因此有一个object_id 方法。

    【讨论】:

    • 当我做了下面的&gt;&gt; local_variables #=&gt; [:name, :_]:_ 是什么?
    • 嗯,它是另一个局部变量。我不能告诉你它来自哪里,你必须为此展示你的整个代码。但是,如果您在 IRb 中尝试过,那么答案很明显:由 IRb 定义的局部变量始终保存最后一个被评估的表达式的值。
    • 是的。我在IRB 中输入了它。这是代码&gt;&gt; local_variables #=&gt; [:_] &gt;&gt; if false &gt;&gt; name = "joy" &gt;&gt; end #=&gt; nil &gt;&gt; local_variables #=&gt; [:name, :_]
    • 好的,我现在试了一下 - local_variables #=&gt; [:_] if false name = "joy" end #=&gt; nil local_variables #=&gt; [:name, :_] a = 2 + 3 #=&gt; 5 local_variables #=&gt; [:a, :name, :_] p :_ :_ #=&gt; :_ 但在 :_ 中看不到任何值 :(
    • 变量名是_:_ 是一个符号文字,表示变量的名称。
    【解决方案2】:

    Ruby 总是解析你的所有代码。它不会将 false 视为不解析内部内容的标志,它会对其进行评估并看到不应执行内部代码

    【讨论】:

    • 你的提示很好!所以+1来自我。 :)
    • 嗯? if false ; TOTALCRAPGOESHERE ; end 导致静音 # ⇒ nil
    • 对。 TOTALCRAPGOESHERE 没有被评估,表达式返回 nil
    • @enthrops 对不起,我不小心把“parsed”读作“尝试评估”而不是“parsed for syntax”。我的错。
    【解决方案3】:

    Ruby 有局部变量“提升”。如果您在方法中的任何地方都对局部变量进行了赋值,那么该变量在方法中的任何地方都存在,甚至在赋值之前,即使赋值从未实际执行。在分配变量之前,它的值为nil

    编辑:

    上述说法并不完全正确。 Ruby 确实有一种变量提升的形式,它会在局部变量赋值存在但未执行时定义一个局部变量。但是,在上述方法中发生赋值的点处不会发现该变量。

    【讨论】:

    • 但是它如何进入If 块,而入口点被false 锁定。谁打破了那把锁?
    • 总是local 变量并非如此。
    • -1。这是错误的。局部变量仅在定义后存在。您可能正在考虑 JavaScript。请参阅stackoverflow.com/a/15111934/2988 进行演示。
    • 我想到的是 Ruby,而不是 Javascript,而 Ruby 确实有一种变量提升的形式。但是,我认为您是正确的,该变量仅在方法中比第一个变量赋值发生的位置更晚地定义。然而,该变量仍然被提升,因为即使没有执行赋值,它也被定义。
    • 澄清 Jörg 上面的评论 - “之后”并不意味着执行方面,而是从左到右、从上到下阅读时出现在源代码中。
    猜你喜欢
    • 2017-05-22
    • 2016-03-05
    • 1970-01-01
    • 2023-03-30
    • 1970-01-01
    • 2011-07-19
    • 1970-01-01
    • 2012-12-16
    • 1970-01-01
    相关资源
    最近更新 更多