为啥实例变量在块内时似乎消失了?
Posted
技术标签:
【中文标题】为啥实例变量在块内时似乎消失了?【英文标题】:Why do instance variables seemingly disappear when inside a block?为什么实例变量在块内时似乎消失了? 【发布时间】:2011-10-08 16:30:17 【问题描述】:原谅我,伙计们。我在 best 是 Ruby 的新手。我只是想知道对我来说似乎很奇怪的行为的解释。
我正在使用 Savon 库与我的 Ruby 应用程序中的 SOAP 服务进行交互。我注意到以下代码(在我编写的用于处理此交互的类中)似乎传递了 empty values 我希望成员字段的值去的地方:
create_session_response = client.request "createSession" do
soap.body =
:user => @user, # This ends up being empty in the SOAP request,
:pass => @pass # as does this.
end
尽管@user
和@pass
都已初始化为非空字符串,但还是会这样做。
当我将代码更改为使用本地变量时,它会按我预期的方式工作:
user = @user
pass = @pass
create_session_response = client.request "createSession" do
soap.body =
:user => user, # Now this has the value I expect in the SOAP request,
:pass => pass # and this does too.
end
我猜这种奇怪的(对我来说)行为一定与我在一个街区内的事实有关;但实际上,我不知道。有人能告诉我这个吗?
【问题讨论】:
【参考方案1】:来自the documentation:
Savon::Client.new 接受一个块,您可以在其中访问局部变量,甚至是您自己的类中的公共方法,但实例变量不起作用。如果您想知道为什么会这样,我建议您阅读有关带有委托的 instance_eval 的内容。
在提出这个问题时可能没有很好的记录。
【讨论】:
【参考方案2】:解决此问题的另一种方法是将对您的对象的引用携带到块中,而不是多次枚举每个需要的属性:
o = self
create_session_response = client.request "createSession" do
soap.body =
:user => o.user,
:pass => o.pass
end
但现在你需要属性访问器。
【讨论】:
【参考方案3】:在第一种情况下,self
的计算结果为 client.request('createSession')
,它没有这些实例变量。
在第二种情况下,变量作为闭包的一部分被带入块中。
【讨论】:
【参考方案4】:首先,@user
不是 Ruby 中的“私有变量”;这是一个instance variable。实例变量在当前对象的范围内可用(self
指的是)。我已编辑您的问题标题,以更准确地反映您的问题。
块就像一个函数,是一组稍后要执行的代码。通常,该块将在定义该块的范围内执行,但也可以在另一个上下文中评估该块:
class Foo
def initialize( bar )
# Save the value as an instance variable
@bar = bar
end
def unchanged1
yield if block_given? # call the block with its original scope
end
def unchanged2( &block )
block.call # another way to do it
end
def changeself( &block )
# run the block in the scope of self
self.instance_eval &block
end
end
@bar = 17
f = Foo.new( 42 )
f.unchanged1 p @bar #=> 17
f.unchanged2 p @bar #=> 17
f.changeself p @bar #=> 42
因此,要么您在设置@user
的范围之外定义块,否则client.request
的实现会导致稍后在另一个范围内评估该块。您可以通过以下方式找到:
client.request("createSession") p [self.class,self]
深入了解您的区块中当前的self
是什么类型的对象。
在您的情况下它们“消失”而不是抛出错误的原因是 Ruby 允许您请求任何实例变量的值,即使该值从未为当前对象设置过。如果从未设置过该变量,您只会返回nil
(如果您启用了它们,则会收到警告):
$ ruby -e "p @foo"
nil
$ ruby -we "p @foo"
-e:1: warning: instance variable @foo not initialized
nil
如您所见,块也是 closures。这意味着当它们运行时,它们可以访问定义在与定义块相同的范围内的局部变量。这就是您的第二组代码按预期工作的原因。闭包是一种很好的锁定值以供以后使用的方法,例如在回调中。
继续上面的代码示例,您可以看到无论在哪个范围内评估块,局部变量都是可用的,并且优先于该范围内的同名方法(除非您提供显式接收器):
class Foo
def x
123
end
end
x = 99
f.changeself p x #=> 99
f.unchanged1 p x #=> 99
f.changeself p self.x #=> 123
f.unchanged1 p self.x #=> Error: undefined method `x' for main:Object
【讨论】:
谢谢!我很难用自己的代码块复制它;看到您使用instance_eval
的示例帮助我清除了它。
@Dan 不客气。我在示例末尾添加了更多内容,展示了简单案例的闭包。
这个问题有很好的解决方案吗?否则我的 N.times 块无法访问实例变量,并且使用 o=self 的解决方法现在变得语法混乱:(
@Konrads 一个很好的解决方案?针对您的代码、需求和愿望提出一个新问题。对别人问题的答案的随机注释和代码的 sn-p 不会给你很好的帮助。以上是关于为啥实例变量在块内时似乎消失了?的主要内容,如果未能解决你的问题,请参考以下文章
为啥在块外设置时,`scheduledTimer` 会正确触发,而不是在块内?