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

发布于 2024-11-25 05:31:05 字数 852 浏览 3 评论 0原文

原谅我,伙计们。对于 Ruby,我最多算是个新手。我只是想知道对我来说似乎很奇怪的行为的解释。

我正在使用 Savon 库与 Ruby 应用程序中的 SOAP 服务进行交互。我注意到以下代码(在我为处理此交互而编写的类中)似乎传递了空值,我期望成员字段的值会去:

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

我猜测这种奇怪的(对我来说)行为一定与我在一个块内的事实有关;但实际上,我不知道。有人可以启发我吗?

Forgive me, guys. I am at best a novice when it comes to Ruby. I'm just curious to know the explanation for what seems like pretty odd behavior to me.

I'm using the Savon library to interact with a SOAP service in my Ruby app. What I noticed is that the following code (in a class I've written to handle this interaction) seems to pass empty values where I expect the values of member fields to go:

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

This is despite the fact that both @user and @pass have been initialized as non-empty strings.

When I change the code to use locals instead, it works the way I expect:

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

I'm guessing this strange (to me) behavior must have something to do with the fact that I'm inside a block; but really, I have no clue. Could someone enlighten me on this one?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

够运 2024-12-02 05:31:05

首先,@user 不是 Ruby 中的“私有变量”;它是一个实例变量。实例变量在当前对象的范围内可用(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

正如您所发现的,块也是闭包。这意味着当它们运行时,它们可以访问与块定义相同的作用域中定义的局部变量。这就是为什么您的第二组代码按预期工作的原因。闭包是一种很好的方法来锁定稍后使用的值,例如在回调中。

继续上面的代码示例,您可以看到,无论块的计算范围如何,局部变量都可用,并且优先于该范围内的同名方法(除非您提供显式接收器):

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

First off, @user is not a "private variable" in Ruby; it is an instance variable. Instance variables are available within the the scope of the current object (what self refers to). I have edited the title of your question to more accurately reflect your question.

A block is like a function, a set of code to be executed at a later date. Often that block will be executed in the scope where the block was defined, but it is also possible to evaluate the block in another context:

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

So either you are defining the block outside the scope where @user is set, or else the implementation of client.request causes the block to be evaluated in another scope later on. You could find out by writing:

client.request("createSession"){ p [self.class,self] }

to gain some insight into what sort of object is the current self in your block.

The reason they "disappear" in your case—instead of throwing an error—is that Ruby permissively allows you to ask for the value of any instance variable, even if the value has never been set for the current object. If the variable has never been set, you'll just get back nil (and a warning, if you have them enabled):

$ ruby -e "p @foo"
nil

$ ruby -we "p @foo"
-e:1: warning: instance variable @foo not initialized
nil

As you found, blocks are also closures. This means that when they run they have access to local variables defined in the same scope as the block is defined. This is why your second set of code worked as desired. Closures are one excellent way to latch onto a value for use later on, for example in a callback.

Continuing the code example above, you can see that the local variable is available regardless of the scope in which the block is evaluated, and takes precedence over same-named methods in that scope (unless you provide an explicit receiver):

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
◇流星雨 2024-12-02 05:31:05

来自文档

Savon::Client.new 接受一个块,您可以在其中访问本地变量,甚至可以访问您自己的类中的公共方法,但实例变量不起作用。如果您想知道为什么会这样,我建议您阅读有关带有委托的 instance_eval 的内容。

当这个问题被问到时,可能没有很好的记录。

From the documentation:

Savon::Client.new accepts a block inside which you can access local variables and even public methods from your own class, but instance variables won’t work. If you want to know why that is, I’d recommend reading about instance_eval with delegation.

Possibly not as well documented when this question was asked.

耳钉梦 2024-12-02 05:31:05

在第一种情况下,self 的计算结果为 client.request('createSession'),它没有这些实例变量。

在第二种情况下,变量作为闭包的一部分被带入块中。

In the first case, self evaluates to client.request('createSession'), which doesn't have these instance variables.

In the second, the variables are brought into the block as part of the closure.

套路撩心 2024-12-02 05:31:05

解决此问题的另一种方法是将对象的引用携带到块中,而不是多次枚举每个所需的属性:

o = self
create_session_response = client.request "createSession" do
  soap.body = {
    :user => o.user,
    :pass => o.pass
  }
end

但现在您需要属性访问器。

Another way to fix the issue would be to carry a reference to your object into the block rather than enumerating each needed attribute more than once:

o = self
create_session_response = client.request "createSession" do
  soap.body = {
    :user => o.user,
    :pass => o.pass
  }
end

But now you need attribute accessors.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文