Программный доступ к переменным по имени в Ruby

Я не совсем уверен, возможно ли это в 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? Все эти объекты представляют собой массивы, поэтому ответы, которые я получил до сих пор, похоже, не работают (за исключением реинжиниринга всего этого, ala ответ dbr)

Если бы я мог это понять, это мог бы быть действительно интересный вопрос. Здесь нам нужен дополнительный контекст. Когда вам нужно, но вы не знаете имя переменной? Можем ли мы сказать «почему» или два назад - какую проблему вы пытаетесь решить?

Mike Woodhouse 12.09.2008 12:15

Это не имеет отношения к вашему вопросу, но это не очень похоже на Ruby для проверки имени класса (Array). Вместо этого вы должны проверить ФУНКЦИОНАЛЬНОСТЬ класса, который вы хотите, чтобы он имел - в этом случае, вероятно, перечислимость.

Max Cantor 21.10.2008 22:42

@Max Cantor, так как лучше всего это проверить? Respond_to что ли?

Dan Rosenstark 02.02.2011 21:11

Это очень плохо, мне нравится идея о том, что переменные знают свои имена. Как-то самореализуются

Kevin 06.04.2013 02:58
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
24
4
28 675
11
Перейти к ответу Данный вопрос помечен как решенный

Ответы 11

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

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

object.instance_variables

или же

self.instance_variables

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

Спасибо, но это не особо помогает. Конкретная проблема, с которой я столкнулся, заключается в том, что у меня есть три переменные, которые я хочу перебрать, и если все они не относятся к определенному типу данных, я хочу распечатать, что это не так, и напечатать имя этой переменной. Спасибо хоть!

Chris Bunch 12.09.2008 12:20

Крис: не могли бы вы перефразировать свой актуальный вопрос выше, чтобы отразить этот вариант использования? Я все еще не совсем понимаю, какова ваша цель.

James A. Rosen 12.09.2008 15:36

Основываясь на Джошмсмур, вероятно, что-то вроде этого:

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

Конечно, он работает с local_variables, но помните, что local_variables для метода включает только те, которые были определены внутри метода.

Dan Rosenstark 02.02.2011 17:32

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

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

Просто замените Fixnum на проверку вашего конкретного типа.

Проблема, с которой я сталкиваюсь, заключается в том, что вы выполняете команду put var. При выполнении команды put на массив выводится содержимое массива, а не имя массива. Мой текущий код выше похож на ваш (хотя я не использовал eval).

Chris Bunch 12.09.2008 20:30

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

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

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

Это возможно, но теперь количество повторений в моем коде теряет всякую выгоду, и я бы с таким же успехом разбил свой код на N сегментов, по одному для каждого массива (а затем жестко закодировал имя массива)

Chris Bunch 12.09.2008 20:23

Вы не можете, вам нужно вернуться к чертежной доске и заново спроектировать свое решение.

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

Chris Bunch 12.09.2008 20:26

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

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

a = 1
get_name(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.

Определенно все действительные баллы (отсюда и голос за). Но посмотрите ответ Гленна, он определенно заслуживает внимания.

Chris Bunch 17.09.2008 09:57

На самом деле я рассматривал эту проблему с точки зрения языка, который изначально был разработан, чтобы рассматривать «переменные» как отдельный тип. Итак, в операторе a = 5 ; b = a есть два «переменных» объекта и одно «атомарное» значение (5). В этой системе b.name => 'b' и b.value => 5. a.name => 'a' и a.value => 5. b = a - это метод = на b, который присваивает значение RHS. a достаточно умен, чтобы знать, что следует назначать ценить, а не указатель a.

colinta 22.06.2013 00:37

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

colinta 22.06.2013 00:39
Ответ принят как подходящий

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

["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 = ...», чтобы сохранить eval от выброса исключения ...

Есть ли здесь альтернатива использованию eval.

Shubham 16.10.2016 07:03

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

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

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 / each_with_index, потому что он ничего не значил. Каждый и each_with_index возвращают массив, с которым они работали.

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

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

Отличный вопрос. Я полностью понимаю вашу мотивацию. Позвольте мне начать с того, что отметим, что существуют определенные виды специальных объектов, которым при определенных обстоятельствах известна переменная, которой они были присвоены. Эти специальные объекты, например. Экземпляры 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 крючок.

Другие вопросы по теме