method - ruby variable scope
Acceda a las variables programáticamente por nombre en Ruby (11)
¿Qué pasa si das la vuelta a tu problema? En lugar de tratar de obtener nombres de variables, obtenga las variables de los nombres:
["foo", "goo", "bar"].each { |param_name|
param = eval(param_name)
if param.class != Array
puts "#{param_name} wasn''t an Array. It was a/an #{param.class}"
return "Error: #{param_name} wasn''t an Array"
end
}
Si existiera la posibilidad de que alguna de las variables no se definiera en absoluto (a diferencia de no ser una matriz), querría agregar "rescate nulo" al final de la línea "param = ..." para mantener la evaluación. de lanzar una excepción ...
No estoy del todo seguro de si esto es posible en Ruby, pero espero que haya una manera fácil de hacerlo. Quiero declarar una variable y luego averiguar el nombre de la variable. Es decir, para este simple fragmento de código:
foo = ["goo", "baz"]
¿Cómo puedo recuperar el nombre de la matriz (aquí, "foo")? Si es posible, ¿funciona esto en cualquier variable (por ejemplo, escalares, hash, etc.)?
Editar: esto es lo que básicamente estoy tratando de hacer. Estoy escribiendo un servidor SOAP que envuelve una clase con tres variables importantes, y el código de validación es esencialmente el siguiente:
[foo, goo, bar].each { |param|
if param.class != Array
puts "param_name wasn''t an Array. It was a/an #{param.class}"
return "Error: param_name wasn''t an Array"
end
}
Mi pregunta es: ¿Puedo reemplazar las instancias de ''param_name'' con foo, goo o bar? Estos objetos son todos matrices, por lo que las respuestas que he recibido hasta ahora no parecen funcionar (a excepción de la reingeniería de toda la respuesta de ala dbr )
Foo es solo una ubicación para mantener un puntero a los datos. Los datos no tienen conocimiento de qué puntos. En los sistemas de Smalltalk podría pedirle a la VM todos los apuntadores a un objeto, pero eso solo le proporcionaría el objeto que contenía la variable foo, no foo. No hay una manera real de hacer referencia a una variable en Ruby. Como se menciona en una respuesta, aún puede colocar una etiqueta en los datos que hace referencia a su origen o similares, pero en general no es una buena referencia para la mayoría de los problemas. Puede usar un hash para recibir los valores en primer lugar, o usar un hash para pasarlo a su ciclo, de modo que conozca el nombre del argumento para fines de validación como en la respuesta de DBR.
Gran pregunta Entiendo completamente tu motivación. Permítanme comenzar señalando que hay ciertos tipos de objetos especiales que, bajo ciertas circunstancias, tienen conocimiento de la variable a la que han sido asignados. Estos objetos especiales son, por ej. Instancias de Module
, instancias de Class
e instancias de Struct
:
Dog = Class.new
Dog.name # Dog
El truco es que esto solo funciona cuando la variable a la que se realiza la asignación es una constante. (Todos sabemos que las constantes de Ruby no son más que variables emocionalmente sensibles.) Por lo tanto:
x = Module.new # creating an anonymous module
x.name #=> nil # the module does not know that it has been assigned to x
Animal = x # but will notice once we assign it to a constant
x.name #=> "Animal"
Este comportamiento de los objetos conscientes de las variables que les han sido asignadas, comúnmente se llama magia constante (porque está limitado a las constantes). Pero esta magia constante altamente deseable solo funciona para ciertos objetos:
Rover = Dog.new
Rover.name #=> raises NoMethodError
Afortunadamente, he escrito una joya y_support/name_magic
, que se encarga de esto:
# first, gem install y_support
require ''y_support/name_magic''
class Cat
include NameMagic
end
El hecho de que esto solo funcione con constantes (es decir, variables que comienzan con una letra mayúscula) no es una limitación tan grande. De hecho, le da libertad para nombrar o no nombrar sus objetos a voluntad:
tmp = Cat.new # nameless kitty
tmp.name #=> nil
Josie = tmp # by assigning to a constant, we name the kitty Josie
tmp.name #=> :Josie
Desafortunadamente, esto no funcionará con los literales de arreglos, porque están construidos internamente sin utilizar el método #new
, en el que se basa NameMagic
. Por lo tanto, para lograr lo que desea, tendrá que subclase Array
:
require ''y_support/name_magic''
class MyArr < Array
include NameMagic
end
foo = MyArr.new ["goo", "baz"] # not named yet
foo.name #=> nil
Foo = foo # but assignment to a constant is noticed
foo.name #=> :Foo
# You can even list the instances
MyArr.instances #=> [["goo", "baz"]]
MyArr.instance_names #=> [:Foo]
# Get an instance by name:
MyArr.instance "Foo" #=> ["goo", "baz"]
MyArr.instance :Foo #=> ["goo", "baz"]
# Rename it:
Foo.name = "Quux"
Foo.name #=> :Quux
# Or forget the name again:
MyArr.forget :Quux
Foo.name #=> nil
# In addition, you can name the object upon creation even without assignment
u = MyArr.new [1, 2], name: :Pair
u.name #=> :Pair
v = MyArr.new [1, 2, 3], ɴ: :Trinity
v.name #=> :Trinity
Logré el comportamiento constante de imitación de la magia buscando todas las constantes en todos los espacios de nombres del espacio de objetos de Ruby actual. Esto desperdicia una fracción de segundo, pero como la búsqueda se realiza solo una vez, no existe una penalización de rendimiento una vez que el objeto adivine su nombre. En el futuro, el equipo central de Ruby ha prometido el const_assigned
.
Lo más parecido a una respuesta real a su pregunta es usar el método Enumerable each_with_index en lugar de cada uno, así:
my_array = [foo, baz, bar]
my_array.each_with_index do |item, index|
if item.class != Array
puts "#{my_array[index]} wasn''t an Array. It was a/an #{item.class}"
end
end
Eliminé la declaración de devolución del bloque que estaba pasando a cada / each_with_index porque no significaba nada. Each y each_with_index devuelven la matriz en la que estaban operando.
También hay algo sobre el alcance en los bloques que vale la pena mencionar aquí: si ha definido una variable fuera del bloque, estará disponible dentro de él. En otras palabras, puede consultar foo, bar y baz directamente dentro del bloque. Lo contrario no es cierto: las variables que cree por primera vez dentro del bloque no estarán disponibles fuera de él.
Finalmente, la sintaxis do / end es preferible para bloques de varias líneas, pero eso es simplemente una cuestión de estilo, aunque es universal en el código ruby de cualquier añada reciente.
Necesita volver a diseñar su solución. Incluso si pudieras hacerlo (no puedes), la pregunta simplemente no tiene una respuesta razonable.
Imagine un método get_name.
a = 1
get_name(a)
Todos probablemente podrían estar de acuerdo en que esto debería devolver ''a''
b = a
get_name(b)
¿Debería devolver ''b'', o ''a'', o una matriz que contenga ambos?
[b,a].each do |arg|
get_name(arg)
end
¿Debería devolver ''arg'', ''b'' o ''a''?
def do_stuff( arg )
get_name(arg)
do
do_stuff(b)
¿Debería devolver ''arg'', ''b'' o ''a'', o tal vez la matriz de todos ellos? Incluso si devolviera una matriz, ¿cuál sería el orden y cómo sabría cómo interpretar los resultados?
La respuesta a todas las preguntas anteriores es "Depende de lo particular que deseo en ese momento". No estoy seguro de cómo podrías resolver ese problema para Ruby.
No puede, necesita regresar al diseño y rediseñar su solución.
No sé de ninguna manera para obtener un nombre de variable local. Pero puede usar el método instance_variables
, esto devolverá una matriz de todos los nombres de variables de instancia en el objeto.
Llamada simple:
object.instance_variables
o
self.instance_variables
para obtener una matriz de todos los nombres de variables de instancia.
OK, también funciona en métodos de instancia y, en función de sus requisitos específicos (el que coloca en el comentario), puede hacer esto:
local_variables.each do |var|
puts var if (eval(var).class != Fixnum)
end
Simplemente reemplace Fixnum
con su comprobación de tipo específico.
Parece que estás tratando de resolver un problema que tiene una solución mucho más fácil ...
¿Por qué no simplemente almacenar los datos en un hash? Si lo haces..
data_container = {''foo'' => [''goo'', ''baz'']}
... entonces es absolutamente trivial obtener el nombre ''foo''.
Dicho esto, no has dado ningún contexto al problema, por lo que puede haber una razón por la que no puedes hacer esto ...
[edit] Después de la aclaración, veo el problema, pero no creo que este sea el problema. Con [foo, bar, bla], es equivalente a decir [''content 1'', ''content 2'', ''etc'']
El nombre real de las variables es (o más bien, debería ser) completamente irrelevante. Si el nombre de la variable es importante, es exactamente por eso que existen hashes.
El problema no es iterar sobre [foo, bar], etc., es el problema fundamental de cómo el servidor SOAP está retomando los datos y / o cómo está tratando de usarlos.
La solución, diría yo, es hacer que el servidor SOAP devuelva hashes, o, ya que sabes que siempre habrá tres elementos, ¿no puedes hacer algo como ...
{"foo" => foo, "goo" => goo, "bar"=>bar}.each do |param_name, param|
if param.class != Array
puts "#{param_name} wasn''t an Array. It was a/an #{param.class}"
puts "Error: #{param_name} wasn''t an Array"
end
end
Sobre la base de joshmsmoore , algo como esto probablemente lo haría:
# Returns the first instance variable whose value == x
# Returns nil if no name maps to the given value
def instance_variable_name_for(x)
self.instance_variables.find do |var|
x == self.instance_variable_get(var)
end
end
Kernel::local_variables
, pero no estoy seguro de que esto funcione para los vars locales de un método, y no sé si puedes manipularlo de tal manera que puedas hacer lo que deseas lograr.