Доступ к переменным программно по имени в Ruby - PullRequest
24 голосов
/ 12 сентября 2008

Я не совсем уверен, возможно ли это в Ruby, но, надеюсь, есть простой способ сделать это. Я хочу объявить переменную, а затем узнать имя переменной. То есть для этого простого фрагмента:

foo = ["goo", "baz"]

Как я могу получить имя массива (здесь, "foo") обратно? Если это действительно возможно, работает ли это с какой-либо переменной (например, скаляры, хэши и т. Д.)?

Редактировать: Вот что я в основном пытаюсь сделать. Я пишу SOAP-сервер, который охватывает класс с тремя важными переменными, и код проверки по сути таков:

  [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
      }

Мой вопрос: могу ли я заменить экземпляры 'param_name' на foo, goo или bar? Все эти объекты являются массивами, поэтому ответы, которые я получил до сих пор, похоже, не работают (за исключением реинжиниринга всего этого аля dbr's answer )

Ответы [ 11 ]

40 голосов
/ 15 сентября 2008

Что если вы перевернете свою проблему? Вместо того, чтобы пытаться получить имена из переменных, получите переменные из имен:

["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
  }

Если бы была вероятность того, что переменные вообще не были определены (в отличие от того, чтобы не быть массивом), вы бы хотели добавить «rescue nil» в конец строки «param = ...» не позволяйте эвалу бросить исключение ...

28 голосов
/ 15 сентября 2008

Вам необходимо изменить архитектуру вашего решения. Даже если вы могли бы сделать это (вы не можете), на вопрос просто нет разумного ответа.

Представьте метод get_name.

a = 1
get_name(a)

Каждый, возможно, согласится, что это должно вернуть 'a'

b = a
get_name(b)

Должен ли он возвращать 'b' или 'a', или массив, содержащий оба?

[b,a].each do |arg|
  get_name(arg)
end

Должно ли оно возвращать 'arg', 'b' или 'a'?

def do_stuff( arg )
  get_name(arg)
do
do_stuff(b)

Должно ли оно возвращать 'arg', 'b' или 'a', или, может быть, массив всех из них? Даже если он вернет массив, каков будет порядок и как я узнаю, как интерпретировать результаты?

Ответ на все вопросы выше: «Это зависит от того, что я хочу в данный момент». Я не уверен, как ты мог решить эту проблему для Ruby.

8 голосов
/ 12 сентября 2008

Кажется, вы пытаетесь решить проблему, решение которой гораздо проще ..

Почему бы просто не сохранить данные в хеше? Если вы делаете ..

data_container = {'foo' => ['goo', 'baz']}

.. тогда очень просто получить имя 'foo'.

Тем не менее, вы не дали никакого контекста этой проблеме, поэтому может быть причина, по которой вы не можете сделать это ..

[править] После разъяснения я вижу проблему, но не думаю, что это проблема .. С [foo, bar, bla] это эквивалентно высказыванию ['content 1', 'content 2', 'etc']. Фактическое имя переменной (точнее, должно быть) совершенно не имеет значения. Если имя переменной важно, именно поэтому существуют хэши.

Проблема не в том, чтобы перебирать [foo, bar] и т. Д., Это фундаментальная проблема в том, как сервер SOAP извлекает данные и / или как вы пытаетесь их использовать.

Решение, я бы сказал, состоит в том, чтобы либо заставить SOAP-сервер возвращать хэши, либо, поскольку вы знаете, что всегда будет три элемента, вы не можете сделать что-то вроде ..

{"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
5 голосов
/ 12 сентября 2008

ОК, он тоже работает в экземплярах методов, и, основываясь на ваших конкретных требованиях (которые вы указали в комментарии), вы можете сделать это:

local_variables.each do |var|
  puts var if (eval(var).class != Fixnum)
end

Просто замените Fixnum на конкретный тип проверки.

2 голосов
/ 10 июня 2013

Отличный вопрос. Я полностью понимаю вашу мотивацию. Позвольте мне вначале отметить, что существуют определенные виды специальных объектов, которые при определенных обстоятельствах знают переменную, которой они были назначены. Эти специальные объекты, например. Module экземпляров, Class экземпляров и Struct экземпляров:

Dog = Class.new
Dog.name # Dog

Суть в том, что это работает только тогда, когда переменная, для которой выполняется присвоение, является константой. (Мы все знаем, что константы Ruby являются не более чем эмоционально чувствительными переменными.) Таким образом:

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"

Такое поведение объектов, которым известно, каким переменным они были назначены, обычно называется константа магии (поскольку она ограничена константами). Но эта крайне желательная постоянная магия работает только для определенных объектов:

Rover = Dog.new
Rover.name #=> raises NoMethodError

К счастью, Я написал камень y_support/name_magic, который позаботится об этом для вас:

 # first, gem install y_support
require 'y_support/name_magic'

class Cat
  include NameMagic
end

Тот факт, что это работает только с константами (то есть переменными, начинающимися с заглавной буквы), не является таким большим ограничением. Фактически, это дает вам свободу называть или не называть ваши объекты по желанию:

tmp = Cat.new # nameless kitty
tmp.name #=> nil
Josie = tmp # by assigning to a constant, we name the kitty Josie
tmp.name #=> :Josie

К сожалению, это не будет работать с литералами массива, потому что они построены внутри без использования метода #new, на который опирается NameMagic. Поэтому, чтобы достичь того, чего вы хотите, вам нужно будет создать подкласс 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

Я добился постоянного имитации магии с помощью поиска во всех константах во всех пространствах имен текущего пространства объектов Ruby. Это приводит к потере доли секунды, но поскольку поиск выполняется только один раз, производительность не снижается, когда объект определяет свое имя. В будущем основная команда Ruby обещала const_assigned hook .

2 голосов
/ 12 сентября 2008

Есть Kernel::local_variables, но я не уверен, что это будет работать для локальных переменных метода, и я не знаю, что вы можете манипулировать им таким образом, чтобы делать то, что вы хотите достичь.

2 голосов
/ 12 сентября 2008

Основываясь на joshmsmoore , что-то вроде этого, вероятно, сделает это:

# 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
2 голосов
/ 12 сентября 2008

Я не знаю ни одного способа получить имя локальной переменной. Но вы можете использовать метод instance_variables, который вернет массив всех имен переменных экземпляра в объекте.

Простой звонок:

object.instance_variables

или

self.instance_variables

чтобы получить массив всех имен переменных экземпляра.

1 голос
/ 16 сентября 2008

Наиболее близким к реальному ответу на ваш вопрос является использование метода Enumerable each_with_index вместо каждого, поэтому:

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

Я удалил оператор return из блока, который вы передавали каждому / each_with_index, потому что он ничего не делал / ничего не значил. Each и each_with_index оба возвращают массив, на котором они работали.

Есть также кое-что о области видимости в блоках, на которую стоит обратить внимание: если вы определили переменную вне блока, она будет доступна внутри нее. Другими словами, вы можете ссылаться на foo, bar и baz непосредственно внутри блока. Обратное неверно: переменные, которые вы впервые создаете внутри блока, не будут доступны за его пределами.

Наконец, синтаксис do / end предпочтителен для многострочных блоков, но это просто вопрос стиля, хотя он универсален в коде ruby ​​любого недавнего винтажа.

1 голос
/ 16 сентября 2008

Foo - это только место для хранения указателя на данные. Данные не знают, что на это указывает. В системах Smalltalk вы можете запросить у VM все указатели на объект, но это даст вам только объект, содержащий переменную foo, а не сам foo. Нет реального способа ссылки на vaiable в Ruby. Как упоминалось в одном ответе, вы можете поместить в данные тег, который ссылается на то, откуда он или что-то подобное, но, как правило, это не очень хорошее решение для большинства проблем. Вы можете использовать хеш для получения значений в первую очередь или использовать хеш для передачи в ваш цикл, чтобы вы знали имя аргумента в целях проверки, как в ответе DBR.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...