在尝试提高我的Ruby技能时,我一直在讨论这个案例,我只能通过阅读API文档找不到解释。非常感谢您的解释。这是示例代码:
for name in [ :new, :create, :destroy ]
define_method("test_#{name}") do
puts name
end
end
我希望/期望发生的是name
变量将绑定到define_method
的块,并且当#test_new
被调用时,它将输出“new”。而是每个定义的方法输出“destroy” - 分配给name变量的最后一个值。我对define_method
及其障碍有什么误解?谢谢!
答案 0 :(得分:6)
Ruby中的块是闭包:传递给define_method
的块会捕获变量name
本身 - 而不是其值 - 因此只要调用该块,它就会保留在作用域中。这是这个难题的第一部分。
第二部分是由define_method
定义的方法块本身。基本上,它将Proc
对象(传递给它的块)转换为Method
对象,并将其绑定到接收器。
所以你最终得到的是一个方法已经捕获(关闭)变量name
,当你的循环完成时,它被设置为:destroy
添加: for ... in
构造实际上会创建一个新的局部变量,相应的[ ... ].each {|name| ... }
构造将不。也就是说,你的for ... in
循环等同于以下(无论如何在Ruby 1.8中):
name = nil
[ :new, :create, :destroy ].each do |name|
define_method("test_#{name}") do
puts name
end
end
name # => :destroy
答案 1 :(得分:1)
for name in [ :new, :create, :destroy ]
local_name = name
define_method("test_#{local_name}") do
puts local_name
end
end
此方法将按预期运行。混淆的原因是每次迭代for循环都不会创建一次'name'。它创建一次,并递增。另外,如果我理解正确,方法定义不是像其他块一样的闭包。它们保留可变可见性,但不会关闭变量的当前值。
答案 2 :(得分:0)
这里的问题是for
循环表达式不会创建新范围。在Ruby中创建新范围的唯一事情是脚本主体,模块主体,类主体,方法主体和块。
如果你真的在草案ISO Ruby规范中查找for
循环表达式的行为,你会发现for
循环表达式的执行方式与each
迭代器 除外,因为它没有创建新的范围。
任何Rubyist都不会使用for
循环:它们会使用迭代器,而 会占用一个块,从而创建一个新的范围。
如果使用惯用迭代器,一切都按预期工作:
class Object
%w[new create destroy].each do |name|
define_method "test_#{name}" do
puts name
end
end
end
require 'test/unit'
require 'stringio'
class TestDynamicMethods < Test::Unit::TestCase
def setup; @old_stdout, $> = $>, (@fake_logdest = StringIO.new) end
def teardown; $> = @old_stdout end
def test_that_the_test_create_method_prints_create
Object.new.test_create
assert_equal "create\n", @fake_logdest.string
end
def test_that_the_test_destroy_method_prints_destroy
Object.new.test_destroy
assert_equal "destroy\n", @fake_logdest.string
end
def test_that_the_test_new_method_prints_new
Object.new.test_new
assert_equal "new\n", @fake_logdest.string
end
end