为什么Ruby在抛出NameError异常后会保留代码评估?

时间:2018-05-13 16:32:12

标签: ruby

我无法向自己解释的简单代码:

puts a if a = 1

这导致

warning: found = in conditional, should be ==
NameError: undefined local variable or method 'a' for main:Object

虽然现在检查a后我们可以看到它已被定义:

a #=> 1

为什么a被分配给1,尽管引发了异常?

来自docs

  

混淆来自无序执行表达式。   首先分配局部变量,然后尝试调用a   不存在的方法[a]。

这部分仍然令人困惑 - 为什么解释器没有检测到已定义的局部变量a,并且仍然试图调用"不存在"方法?如果它也不检查局部变量,找到定义的局部变量a并打印1

2 个答案:

答案 0 :(得分:5)

让我们看看Ruby的修饰符if的抽象语法树:

$ ruby --dump=parsetree -e 'puts a if a = 1'

# @ NODE_SCOPE (line: 1, code_range: (1,0)-(1,15))
# +- nd_tbl: :a
# +- nd_args:
# |   (null node)
# +- nd_body:
#     @ NODE_PRELUDE (line: 1, code_range: (1,0)-(1,15))
#     +- nd_head:
#     |   (null node)
#     +- nd_body:
#     |   @ NODE_IF (line: 1, code_range: (1,0)-(1,15))
#     |   +- nd_cond:
#     |   |   @ NODE_DASGN_CURR (line: 1, code_range: (1,10)-(1,15))
#     |   |   +- nd_vid: :a
#     |   |   +- nd_value:
#     |   |       @ NODE_LIT (line: 1, code_range: (1,14)-(1,15))
#     |   |       +- nd_lit: 1
#     |   +- nd_body:
#     |   |   @ NODE_FCALL (line: 1, code_range: (1,0)-(1,6))
#     |   |   +- nd_mid: :puts
#     |   |   +- nd_args:
#     |   |       @ NODE_ARRAY (line: 1, code_range: (1,5)-(1,6))
#     |   |       +- nd_alen: 1
#     |   |       +- nd_head:
#     |   |       |   @ NODE_VCALL (line: 1, code_range: (1,5)-(1,6))
#     |   |       |   +- nd_mid: :a
#     |   |       +- nd_next:
#     |   |           (null node)
#     |   +- nd_else:
#     |       (null node)
#     +- nd_compile_option:
#         +- coverage_enabled: false

标准if

$ ruby --dump=parsetree -e 'if a = 1 then puts a end'

# @ NODE_SCOPE (line: 1, code_range: (1,0)-(1,24))
# +- nd_tbl: :a
# +- nd_args:
# |   (null node)
# +- nd_body:
#     @ NODE_PRELUDE (line: 1, code_range: (1,0)-(1,24))
#     +- nd_head:
#     |   (null node)
#     +- nd_body:
#     |   @ NODE_IF (line: 1, code_range: (1,0)-(1,24))
#     |   +- nd_cond:
#     |   |   @ NODE_DASGN_CURR (line: 1, code_range: (1,3)-(1,8))
#     |   |   +- nd_vid: :a
#     |   |   +- nd_value:
#     |   |       @ NODE_LIT (line: 1, code_range: (1,7)-(1,8))
#     |   |       +- nd_lit: 1
#     |   +- nd_body:
#     |   |   @ NODE_FCALL (line: 1, code_range: (1,14)-(1,20))
#     |   |   +- nd_mid: :puts
#     |   |   +- nd_args:
#     |   |       @ NODE_ARRAY (line: 1, code_range: (1,19)-(1,20))
#     |   |       +- nd_alen: 1
#     |   |       +- nd_head:
#     |   |       |   @ NODE_DVAR (line: 1, code_range: (1,19)-(1,20))
#     |   |       |   +- nd_vid: :a
#     |   |       +- nd_next:
#     |   |           (null node)
#     |   +- nd_else:
#     |       (null node)
#     +- nd_compile_option:
#         +- coverage_enabled: false

唯一的区别是puts的方法参数:

#     |   |       |   @ NODE_VCALL (line: 1, code_range: (1,5)-(1,6))
#     |   |       |   +- nd_mid: :a

VS

#     |   |       |   @ NODE_DVAR (line: 1, code_range: (1,19)-(1,20))
#     |   |       |   +- nd_vid: :a

使用修饰符if,解析器将a视为方法调用并创建NODE_VCALL。这指示解释器进行方法调用(尽管 是局部变量a),从而产生NameError。 (因为没有方法a

使用标准if,解析器将a视为局部变量并创建NODE_DVAR。这指示解释器查找一个按预期工作的局部变量。

如您所见,Ruby在解析器级别识别局部变量。这就是文档说的原因:(强调添加)

  

由于解析顺序,修饰符和标准版本[...]不是彼此的精确转换。

答案 1 :(得分:2)

Ruby从左到右解析代码。在解析对它们的第一次赋值时,定义局部变量。在puts a,尚未解析a的赋值,因此局部变量a尚不存在,Ruby假定a是方法调用。局部变量仅存在于以下分配。

在运行时,Ruby必须评估条件以确定是否执行puts,因此a被初始化为1

您似乎在某种REPL中执行该代码。通常,REPL抢救异常而不是终止,这就是为什么你的代码继续执行而不是终止,并且因为我们现在低于赋值,所以定义了变量,并且自从执行赋值以来,变量被初始化。

如果您不清楚变量的定义初始化之间的区别,请冥想:

foo
# NameError

if false
  foo = 42
end

foo
#=> nil

foo = :bar

foo
#=> :bar