可以在块内更改变量吗?

时间:2009-07-01 08:57:31

标签: ruby

我们直接转到代码:

#!/usr/bin/ruby
 require 'tk'


class Epg

def initialize
    @var = "bad"    
    @cvs = nil  
    @items_demo = TkRoot.new() {title "EPG"}
    TkFrame.new(@items_demo) {|cf|
          @var = "good" 
          @cvs = TkCanvas.new(cf) {|c|}
        puts  "@cvs 1  is #{@cvs}"
        puts  "@var 1 is #{@var}"
    }.pack('side'=>'top', 'fill'=>'both', 'expand'=>'yes')

    puts  "@cvs 2 is #{@cvs}"
    puts  "@var 2 is #{@var}"

end #initialize

def test
    @var = "bad"
    puts " @var 3 :#{@var}"
    (1..3).each {|x| @var="good"}
    puts " @var 4 :#{@var}"
end
 end

 e= Epg.new 
 e.test 

这是输出:

@cvs 1  is #<Tk::Canvas:0xb7cecb08>
@var 1 is good
@cvs 2 is 
@var 2 is bad           #@var has NOT been changed by the code in the block
@var 3 :bad
@var 4 :good            #@var has been changed by the code in the block

为什么我们在这里看到不同的行为?

2 个答案:

答案 0 :(得分:5)

您可以将块视为关闭局部变量集和当前self

在Ruby中,无论如何,始终都可以访问本地变量。 self封装了当前对象的实例方法以及实例变量。

请考虑以下代码:

class Table
  def initialize(legs)
    @legs = legs
  end

  def with_legs
    yield @legs
  end
end

然后:

def some_calling_method
  name = "Yehuda"
  Table.new(4) {|legs| puts "#{name} gnaws off one of the #{legs} legs" }
end

通过Ruby的块语义,您可以放心,name将在块中可用,即使不查看您正在调用的方法。

但是,请考虑以下事项:

class Person
  def initialize(name)
    @name = name
  end

  def gnaw
    Table.new(4).with_legs do |legs| 
      puts "#{@name} gnaws off one of the #{legs} legs"
    end
  end
end

Person.new("Yehuda").gnaw

在这种情况下,我们从块内部访问@name实例变量。它在这种情况下效果很好,但不能保证。如果我们以不同的方式实现表格会怎么样:

class Table
  def initialize(legs)
    @legs = legs
  end

  def with_legs(&block)
    self.instance_eval(&block)
  end
end

实际上,我们所说的是“在不同自我的背景下评估块。”在这种情况下,我们在表的上下文中评估块。你为什么这样做?

class Leg
  attr_accessor :number
  def initialize(number)
    @number = number
  end
end

class Table
  def initialize(legs)
    @legs = legs
  end

  def with_leg(&block)
    Leg.new(rand(@legs).instance_eval(&block)
  end
end

现在,您可以这样做:

class Person
  def initialize(name)
    @name = name
  end

  def gnaw
    Table.new(4).with_leg do
      puts "I'm gnawing off one of leg #{number}"
    end
  end
end

如果你想访问块内的person对象,你必须这样做:

class Person
  def initialize(name)
    @name = name
  end

  def gnaw
    my_name = name
    Table.new(4).with_leg do
      puts "#{my_name} is gnawing off one of leg #{number}"
    end
  end
end

正如您所看到的,使用instance_eval可以使访问块内远端对象的方法变得更简单,体积更小,但代价是self无法访问。这种技术通常用在DSL中,其中有许多方法被注入到块中,但是自我并不重要。

这就是Tk发生的事情;他们正在使用instance_eval将自己的self注入到块中,这会擦除你的self干净。

答案 1 :(得分:4)

解释是TkFrame.new使用instance_eval,因此赋值@var =“good”会更改TkFrame的实例变量。试试这个:

class A
  def initialize(&b)
    instance_eval(&b)
  end
end

class B
  def initialize
    @x = 10
    @a = A.new do
      @x = 20
    end
  end
end

p B.new

这是你会看到的:

#<B:0x10141314 @x=10, @a=#<A:0x10141300 @x=20>>