为啥实例变量在块内时似乎消失了?

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 不会给你很好的帮助。

以上是关于为啥实例变量在块内时似乎消失了?的主要内容,如果未能解决你的问题,请参考以下文章

在块内,__block 变量和静态变量之间的实际区别是啥?

访问块内的实例变量

为啥在块外设置时,`scheduledTimer` 会正确触发,而不是在块内?

为啥通过 python 默认变量初始化变量会在对象实例化过程中保持状态?

在块中复制和自动释放本地变量

如何在块内制作边框底部? [关闭]