与错误的`if`块内的赋值操作混淆

时间:2013-03-03 07:48:31

标签: ruby if-statement nameerror

我正在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块中分配。我试图查看xp 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]

3 个答案:

答案 0 :(得分:8)

在Ruby中,局部变量在首次遇到赋值时由解析器定义,然后从该点开始在范围内。

这是一个小小的演示:

foo # NameError: undefined local variable or method `foo' for main:Object

if false
  foo = 42
end

foo # => nil

如您所见,即使第4行的赋值从未执行过,第7行也存在局部变量 。然而,它是解析,这就是本地变量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

您在此处收到错误的原因是,将整齐的局部变量评估为nilfnord是不明确的:它可能是< em>要么无参数消息发送到默认接收器(即等效于self.fnord()对本地变量fnord的访问。

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

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

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

if false
  fnord = 42
end
fnord              #=> nil

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

答案 1 :(得分:3)

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

答案 2 :(得分:1)

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

编辑:

以上不太正确。 Ruby确实有一种变量提升形式,它会在存在局部变量赋值时定义局部变量,但不会执行。但是,不会发现在上面发生分配的方法中的点定义变量。