2009-07-01 9 views
2

Partons au code directement:Une variable peut-elle être modifiée dans un bloc?

#!/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 

est ici la sortie:

@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 

Pourquoi nous voyons un comportement différent ici?

Répondre

4

Vous pouvez penser à blocs que la fermeture à la fois sur l'ensemble des variables locales et la self actuelle.

Dans Ruby, vous aurez toujours accès aux variables locales, quoi qu'il arrive. Le self encapsule des méthodes d'instance sur l'objet actuel ainsi que des variables d'instance.

Consultez le code suivant:

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

    def with_legs 
    yield @legs 
    end 
end 

Et puis:

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

par la sémantique de blocs de Ruby, vous pouvez être assuré que name sera disponible à l'intérieur du bloc, même sans regarder la méthode vous appelez.

Cependant, tenez compte des éléments suivants:

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 

Dans ce cas, nous accèdent à la variable d'instance @name à l'intérieur du bloc. Cela fonctionne très bien dans ce cas, mais n'est pas garanti. Et si nous avons mis la table un peu différemment:

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

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

En effet, ce que nous disons « évaluer le bloc dans le contexte d'un différent soi ». Dans ce cas, nous évaluons le bloc dans le contexte de la table. Pourquoi ferais-tu ça?

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 

Maintenant, vous pouvez faire:

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 

Si vous voulez accéder à l'objet de personne à l'intérieur du bloc, vous auriez à faire:

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 

Comme vous pouvez voyez, l'utilisation de instance_eval peut rendre plus simple et moins encombrant l'accès aux méthodes d'un objet lointain à l'intérieur d'un bloc, mais au prix de rendre le self inaccessible. La technique est généralement utilisée dans les DSL, où un certain nombre de méthodes sont injectées dans le bloc, mais le soi n'a pas beaucoup d'importance.

C'est ce qui se passe avec Tk; ils utilisent instance_eval pour injecter leur propre self dans le bloc, ce qui efface votre self propre.

+0

Merci, Katz. J'ai besoin de temps pour digérer tout ce que tu as dit ... – pierrotlefou

4

L'explication est que TkFrame.new utilise instance_eval et que l'affectation @var = "good" change la variable d'instance de TkFrame. Essayez ceci:

 
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 

Voici ce que vous verrez:

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

Avez-vous une erreur? Est-ce que ça devrait être "l'assignation @var =" bon "_did not_ change la variable d'instance de TkFrame". Comme @x est toujours 10 mais pas 20 dans votre exemple. – pierrotlefou

+0

Non, il n'y a pas de faute de frappe. –

+0

YSE.l'attribution @var = "bon" change la variable d'instance de TkFrame mais ne peut pas changer la variable intance de Epg. – pierrotlefou

Questions connexes