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

StackOverflow https://stackoverflow.com/questions/58482

  •  09-06-2019
  •  | 
  •  

Вопрос

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

Это было полезно?

Решение

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

["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. от выдачи исключения...

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

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

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

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

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

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

Хорошо, это ДЕЙСТВИТЕЛЬНО работает и в методах экземпляра, и, исходя из вашего конкретного требования (того, которое вы указали в комментарии), вы можете сделать это:

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

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

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

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

object.instance_variables

или

self.instance_variables

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top