Question

Je traite un fichier texte basé sur des enregistrements :je recherche donc une chaîne de départ qui constitue le début d'un enregistrement :il n'y a pas de marqueur de fin d'enregistrement, j'utilise donc le début de l'enregistrement suivant pour délimiter le dernier enregistrement.

J'ai donc construit un programme simple pour faire cela, mais je vois quelque chose qui me surprend :il semble que Ruby oublie l'existence des variables locales - ou ai-je trouvé une erreur de programmation ?[même si je ne pense pas avoir :si je définis la variable 'message' avant ma boucle, je ne vois pas l'erreur].

Voici un exemple simplifié avec un exemple de données d'entrée et un message d'erreur dans les commentaires :

flag=false
# message=nil # this is will prevent the issue.
while line=gets do
    if line =~/hello/ then
        if flag==true then
            puts "#{message}"
        end
        message=StringIO.new(line);
        puts message
        flag=true
    else
        message << line
    end
end

# Input File example:
# hello this is a record
# this is also part of the same record
# hello this is a new record
# this is still record 2
# hello this is record 3 etc etc
# 
# Error when running: [nb, first iteration is fine]
# <StringIO:0x2e845ac>
# hello
# test.rb:5: undefined local variable or method `message' for main:Object (NameError)
#
Était-ce utile?

La solution

Je pense que ce message est parce que défini dans la boucle. A la fin de l'itération en boucle « message » est hors de portée. Définition de « message » en dehors de la boucle arrête la variable de sortir du champ d'application à la fin de chaque itération de la boucle. Donc, je pense que vous avez la bonne réponse.

Vous pouvez sortir la valeur du message au début de chaque itération de boucle pour tester si ma suggestion est correcte.

Autres conseils

A partir du langage de programmation Ruby:

alt texte http://bks0.books.google.com/books ? id = jcUbTcr5XWwC & printsec = frontcover & img = 1 & zoom = 5 & sig = ACfU3U1rnYKha_p7vEkpPm1Ow3o9RAM0nQ

Blocs et portée variable

Les blocs définissent une nouvelle portée variable: les variables créées dans un bloc existent uniquement dans ce bloc et ne sont pas définies en dehors du bloc. cependant être prudent,; les variables locales dans un procédé sont disponibles pour tous les blocs au sein de cette méthode. Donc, si un bloc attribue une valeur à une variable qui est déjà définie en dehors du bloc, cela ne crée pas une nouvelle variable de bloc local, mais attribue à la place une nouvelle valeur à la variable déjà existante. Parfois, c'est exactement le comportement que nous voulons:

total = 0   
data.each {|x| total += x }  # Sum the elements of the data array
puts total                   # Print out that sum

Parfois, cependant, nous ne voulons pas modifier les variables dans la portée englobante, mais nous le faisons par inadvertance. Ce problème est particulièrement préoccupant pour les paramètres de bloc dans Ruby 1.8. Dans Ruby 1.8, si un paramètre de bloc partage le nom d'une variable existante, puis invocations du bloc attribuent simplement une valeur à cette variable plutôt que de créer une nouvelle variable de bloc local. Le code suivant, par exemple, est problématique, car elle utilise le même identificateur i en tant que paramètre de bloc pour deux blocs imbriqués:

1.upto(10) do |i|         # 10 rows
  1.upto(10) do |i|       # Each has 10 columns
    print "#{i} "         # Print column number
  end
  print " ==> Row #{i}\n" # Try to print row number, but get column number
end

Ruby 1.9 est différent: les paramètres de bloc sont toujours locaux à leur bloc, et invocations du bloc jamais attribuer des valeurs aux variables existantes. Si Ruby 1.9 est appelé avec le drapeau -w, il vous avertit si un paramètre de bloc a le même nom qu'une variable existante. Cela vous aide à éviter l'écriture de code qui fonctionne différemment dans 1.8 et 1.9.

Ruby 1.9 est différent d'une autre manière importante aussi. syntaxe du bloc a été étendu pour vous permettre de déclarer des variables de bloc local qui sont garantis pour être locale, même si une variable du même nom existe déjà dans le cadre englobante. Pour ce faire, suivez la liste des paramètres du bloc avec un point-virgule et une liste séparée par des virgules bloc variables locales. Voici un exemple:

x = y = 0            # local variables
1.upto(4) do |x;y|   # x and y are local to block
                     # x and y "shadow" the outer variables
  y = x + 1          # Use y as a scratch variable
  puts y*y           # Prints 4, 9, 16, 25
end
[x,y]                # => [0,0]: block does not alter these

Dans ce code, x est un paramètre de bloc: il obtient une valeur lorsque le bloc est invoquée avec un rendement. y est une variable de bloc local. Il ne reçoit aucune valeur d'un appel de rendement, mais il a la valeur nil jusqu'à ce que le bloc attribue effectivement une autre valeur. Le point de déclarer ces variables de bloc local est de garantir que vous ne serez pas écraserait par inadvertance la valeur d'une variable existante. (Cela peut se produire si un bloc est coupé et-collé d'une méthode à une autre, par exemple.) Si vous appelez Ruby 1.9 avec l'option -w, il vous avertit si une variable shadows bloc local une variable existante.

Les blocs peuvent avoir plus d'un paramètre et plus d'une variable locale, bien sûr. Voici un bloc avec deux paramètres et trois variables locales:

hash.each {|key,value; i,j,k| ... }

Contrairement à certaines des autres réponses, while les boucles ne créent pas réellement une nouvelle portée.Le problème que vous voyez est plus subtil.

Pour aider à montrer le contraste, les blocs sont passés à un appel de méthode FAIRE créez une nouvelle portée, de telle sorte qu'une variable locale nouvellement attribuée à l'intérieur du bloc disparaisse après la sortie du bloc :

### block example - provided for contrast only ###
[0].each {|e| blockvar = e }
p blockvar  # NameError: undefined local variable or method

Mais while les boucles (comme votre cas) sont différentes :

arr = [0]
while arr.any?
  whilevar = arr.shift
end
p whilevar  # prints 0

La raison pour laquelle vous obtenez l'erreur dans votre cas est que la ligne qui utilise message:

puts "#{message}"

apparaît avant tout code qui assigne message.

C'est la même raison pour laquelle ce code génère une erreur si a n'a pas été défini au préalable :

# Note the single (not double) equal sign.
# At first glance it looks like this should print '1',
#  because the 'a' is assigned before (time-wise) the puts.
puts a if a = 1

Pas de portée, mais de visibilité par analyse

Le soi-disant "problème" - c'est-à-direvariable locale visibilité dans un seul périmètre - est dû à Ruby analyseur.Puisque nous ne considérons qu'un seul périmètre, les règles de cadrage ont rien à voir avec le problème.Au stade de l'analyse, l'analyseur décide à quels emplacements source une variable locale est visible, et cette visibilité ne pas changement au cours de l'exécution.

Lors de la détermination si une variable locale est définie (c.-à-d. defined? renvoie vrai) à tout moment du code, l'analyseur vérifie la portée actuelle pour voir si un code lui a déjà été attribué, même si ce code n'a jamais été exécuté (l'analyseur ne peut rien savoir de ce qui a été exécuté ou non à ce moment-là. l'étape d'analyse)."Avant" signifie :sur une ligne au-dessus, ou sur la même ligne et à gauche.

Un exercice pour déterminer si un local est défini (c.-à-d.visible)

Notez que ce qui suit s'applique uniquement aux variables locales, pas aux méthodes.(Déterminer si une méthode est définie dans une portée est plus complexe, car cela implique de rechercher les modules inclus et les classes ancêtres.)

Une façon concrète de voir le comportement des variables locales est d'ouvrir votre fichier dans un éditeur de texte.Supposons également qu'en appuyant plusieurs fois sur la touche flèche gauche, vous puissiez déplacer votre curseur vers l'arrière dans l'ensemble du fichier.Supposons maintenant que vous vous demandiez si une certaine utilisation de message augmentera le NameError.Pour ce faire, positionnez votre curseur à l'endroit que vous utilisez message, puis continuez à appuyer sur la flèche gauche jusqu'à ce que vous :

  1. atteindre le début de la portée actuelle (vous devez comprendre les règles de portée de Ruby afin de savoir quand cela se produit)
  2. atteindre le code qui attribue message

Si vous avez atteint une mission avant d'atteindre la limite de la portée, cela signifie que vous utilisez message ne soulèvera pas NameError.Si vous n'atteignez aucune mission, l'utilisation augmentera NameError.

Autres considérations

Dans le cas où une affectation de variable apparaît dans le code mais n'est pas exécutée, la variable est initialisée à nil:

# a is not defined before this
if false
  # never executed, but makes the binding defined/visible to the else case
  a = 1
else
  p a  # prints nil
end 

Cas de test de la boucle While

Voici un petit cas de test pour démontrer l'étrangeté du comportement ci-dessus lorsqu'il se produit dans une boucle while.La variable affectée ici est dest_arr.

arr = [0,1]
while n = arr.shift
  p( n: n, dest_arr_defined: (defined? dest_arr) )

  if n == 0
    dest_arr = [n]
  else
    dest_arr << n
    p( dest_arr: dest_arr )
  end
end

qui produit :

{:n=>0, :dest_arr_defined=>nil}
{:n=>1, :dest_arr_defined=>nil}
{:dest_arr=>[0, 1]}

Les points saillants :

  • La première itération est intuitive, dest_arr est initialisé à [0].
  • Mais nous devons être très attentifs lors de la deuxième itération (lorsque n est 1):
    • Au début, dest_arr n'est pas défini !
    • Mais quand le code atteint le else cas, dest_arr est à nouveau visible, car l'interprète voit qu'il a été défini au préalable (2 lignes).
    • Remarquez également que dest_arr est seulement caché au début de la boucle ;sa valeur n'est jamais perdue.

Cela explique également pourquoi attribuer votre section locale avant le while la boucle résout le problème.La mission n'a pas besoin d'être exécutée ;il suffit qu'il apparaisse dans le code source.

Exemple Lambda

f1 = ->{ f2 }
f2 = ->{ f1 }
p f2.call()
# Fails because the body of f1 tries to access f2 before an assignment for f2 was seen by the parser.
p f1.call()  # undefined local variable or method `f2'.

Corrigez cela en mettant un f2 mission avant f1le corps.N'oubliez pas qu'il n'est pas nécessaire d'exécuter la mission !

f2 = nil  # Could be replaced by: if false; f2 = nil; end
f1 = ->{ f2 }
f2 = ->{ f1 }
p f2.call()
p f1.call()  # ok

Le piège du masquage de méthode

Les choses deviennent vraiment compliquées si vous avez une variable locale portant le même nom qu'une méthode :

def dest_arr
  :whoops
end

arr = [0,1]
while n = arr.shift
  p( n: n, dest_arr: dest_arr )

  if n == 0
    dest_arr = [n]
  else
    dest_arr << n
    p( dest_arr: dest_arr )
  end
end

Les sorties:

{:n=>0, :dest_arr=>:whoops}
{:n=>1, :dest_arr=>:whoops}
{:dest_arr=>[0, 1]}

Une affectation de variable locale dans une portée "masquera"/"ombrera" un appel de méthode du même nom.(Vous pouvez toujours appeler la méthode en utilisant des parenthèses explicites ou un récepteur explicite.) Ceci est donc similaire au précédent while test en boucle, sauf qu'au lieu de devenir indéfini au-dessus du code d'affectation, le dest_arr méthode devient "non masqué"/"non masqué" afin que la méthode puisse être appelée sans parenthèses.Mais tout code après l'affectation verra la variable locale.

Quelques bonnes pratiques que nous pouvons tirer de tout cela

  • Ne nommez pas les variables locales de la même manière que les noms de méthodes dans la même portée
  • Ne mettez pas l'affectation initiale d'une variable locale dans le corps d'un while ou for boucle, ou tout ce qui provoque un saut d'exécution dans une portée (appel de lambdas ou Continuation#call peut le faire aussi).Mettez l'affectation avant la boucle.

Pourquoi pensez-vous que cela est un bug? L'interprète vous dit ce message peut être indéfini lorsque ce morceau particulier de l'exécution du code.

Je ne sais pas pourquoi vous êtes surpris: sur la ligne 5 (en supposant que la ligne de message = nil n'est pas là), vous utilisez potentiellement une variable que l'interprète n'a jamais entendu parler auparavant. L'interprète dit: « Qu'est-ce message? Ce n'est pas une méthode que je sais, ce n'est pas une variable que je sais, ce n'est pas un mot-clé ... » et vous obtenez un message d'erreur.

Voici un exemple simple pour vous montrer ce que je veux dire:

while line = gets do
  if line =~ /./ then
    puts message # How could this work?
    message = line
  end
end

Ce qui donne:

telemachus ~ $ ruby test.rb < huh 
test.rb:3:in `<main>': undefined local variable or method `message' for main:Object (NameError)

En outre, si vous voulez préparer la voie à message, je l'initialiser comme message = '', de sorte que c'est une chaîne (plutôt que nil). Dans le cas contraire, si votre première ligne ne pas Match bonjour, vous finissez par Tring ajouter line à nil - qui vous obtenez cette erreur:

telemachus ~ $ ruby test.rb < huh 
test.rb:4:in `<main>': undefined method `<<' for nil:NilClass (NoMethodError)

vous pouvez simplement faire ceci:

message=''

while line=gets do
   if line =~/hello/ then
      # begin a new record 
      p message unless message == ''
      message = String.new(line)
   else
     message << line
  end
end

# hello this is a record
# this is also part of the same record
# hello this is a new record
# this is still record 2
# hello this is record 3 etc etc
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top