Ruby“定义?”运算符错误?
所以,我们有代码:
class Foo def bar puts "Before existent: #{(defined? some_variable)}" puts "Before not_existent: #{(defined? nonexistent_variable)}" raise "error" some_variable = 42 rescue puts "exception" ensure puts "Ensure existent: #{(defined? some_variable)}" puts "Ensure not_existent: #{(defined? nonexistent_variable)}" end end
并从irb调用它:
> Foo.new.bar
而且,这将返回:
Before existent: Before not_existent: exception Ensure existent: local-variable Ensure not_existent: => nil
现在问题 – 为什么? 我们之前提出了exception,而不是some_variable
。 为什么这样工作? 为什么在ensure块中定义some_variable
? (顺便说一下,它定义为零)
更新:感谢@Max的回答,但如果我们更改代码以使用实例变量:
class Foo def bar puts "Before existent: #{(defined? @some_variable)}" puts "Before not_existent: #{(defined? @nonexistent_variable)}" raise "error" @some_variable = 42 ensure puts "Ensure existent: #{(defined? @some_variable)}" puts "Ensure not_existent: #{(defined? @nonexistent_variable)}" end end
它按预期工作:
Before existent: Before not_existent: Ensure existent: Ensure not_existent:
为什么?
首先要注意的是defined?
是关键字 ,而不是方法。 这意味着在构造语法树时(例如, if
, return
, next
等)在编译期间由解析器专门处理 ,而不是在运行时动态查找。
这就是defined?
原因defined?
可以处理通常会引发错误的表达式:已defined?(what is this even) #=> nil
因为解析器可以从正常的评估过程中排除其参数。
真正令人困惑的是,即使它是一个关键字,它的行为仍然在运行时确定 。 它使用解析器魔术来确定其参数是实例变量,常量,方法等,然后调用普通的Ruby方法来确定是否在运行时定义了这些特定类型:
// ... case DEFINED_GVAR: if (rb_gvar_defined(rb_global_entry(SYM2ID(obj)))) { expr_type = DEFINED_GVAR; } break; case DEFINED_CVAR: // ... if (rb_cvar_defined(klass, SYM2ID(obj))) { expr_type = DEFINED_CVAR; } break; case DEFINED_CONST: // ... if (vm_get_ev_const(th, klass, SYM2ID(obj), 1)) { expr_type = DEFINED_CONST; } break; // ...
rb_cvar_defined
函数与Module#class_variable_defined?
调用的函数相同Module#class_variable_defined?
, 例如。
如此defined?
太奇怪了。 真的很奇怪。 它的行为可能会有很大差异,具体取决于它的论点,我甚至不打赌它在不同的Ruby实现中是相同的。 基于此我建议不要使用它而是使用Ruby的*_defined?
方法尽可能。