Подтвердить что ты не робот

Доступ к переменным программным путем по имени в 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)

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

Представьте метод 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.

Ответ 3

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

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

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

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

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

[edit] После выяснения, я вижу проблему, но я не думаю, что это проблема. С [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

Ответ 4

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

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

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

Ответ 5

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

Простой вызов:

object.instance_variables

или

self.instance_variables

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

Ответ 6

Основываясь на 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

Ответ 7

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

Ответ 8

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

Ответ 9

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

Ответ 10

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

Ответ 11

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

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

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