【问题标题】:Access variables programmatically by name in Ruby在 Ruby 中按名称以编程方式访问变量
【发布时间】:2010-09-08 16:17:46
【问题描述】:

我不完全确定这在 Ruby 中是否可行,但希望有一种简单的方法可以做到这一点。我想声明一个变量,然后找出变量的名称。也就是说,对于这个简单的sn-p:

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
      }

然后我的问题是:我可以用 foo、goo 或 bar 替换“param_name”的实例吗?这些对象都是数组,所以到目前为止我收到的答案似乎不起作用(除了重新设计整个事情ala dbr's answer

【问题讨论】:

  • 如果我能理解的话,这可能是一个非常有趣的问题。我们需要更多的上下文。您什么时候需要但不知道变量的名称?我们可以回溯一两个“为什么”吗?您要解决什么问题?
  • 这与您的问题无关,但是针对类名(数组)进行测试并不是很 Ruby-ish。相反,您应该针对您希望它具有的类的 FUNCTIONALITY 进行测试——在这种情况下,可能是可枚举性。
  • @Max Cantor,那么正确的测试方法是什么? Respond_to 还是什么?
  • 太糟糕了,我喜欢变量知道自己名字的想法。以某种方式感到自我实现

标签: ruby variables


【解决方案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 = ...”行的末尾添加“rescue nil”来防止 eval 抛出异常...

【讨论】:

  • 这里有没有使用 '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 解决这个问题。

【讨论】:

  • 肯定是所有有效积分(因此赞成)。但看看格伦的回答,它当然值得。
  • 实际上,我从一开始就设计的语言的角度考虑了这个问题,将“变量”视为一种单独的类型。所以在声明a = 5 ; b = a 中,有两个“变量”对象和一个“原子”值(5)。在此系统中,b.name => 'b'b.value => 5a.name => 'a'a.value => 5。然后b = ab 上的= 方法,用于分配RHS 的值。 a 足够聪明,知道应该分配的是 ,而不是 a 指针。
  • 我认为支持这一点的语言将有另一种机制来查找父范围,并可能从那里获得一个新名称。那不是很有趣吗?
【解决方案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

【讨论】:

  • 这是可能的,但是现在在我的代码中重复自己的数量失去了任何好处,我也可以将我的代码分成 N 个段,每个数组一个(和然后硬编码数组的名称)
【解决方案4】:

好的,它也可以在实例方法中工作,并且根据您的具体要求(您在评论中提出的要求),您可以这样做:

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

只需将 Fixnum 替换为您的特定类型检查即可。

【讨论】:

  • 我遇到的问题是当您执行“puts var”时。对 Array 执行 puts 会打印出 Array 的内容,而不是 Array 的名称。我上面的当前代码与您的相似(尽管我没有使用 eval)。
【解决方案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,但我不确定这是否适用于方法的本地变量,而且我不知道您是否可以通过操作它来实现您希望实现的目标。

    【讨论】:

    • 当然它适用于 local_variables,但请记住,方法的 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
    

    幸运的是,I have written a gem 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核心团队has promised const_assigned hook.

    【讨论】:

      【解决方案9】:

      你不能,你需要重新设计你的解决方案。

      【讨论】:

      • 这就是我害怕的。我只是希望,由于我是 Ruby 新手,可能会有一些我还没有听说过的简单方法名称。
      【解决方案10】:

      Foo 只是一个保存指向数据的指针的位置。数据不知道它指向什么。在 Smalltalk 系统中,您可以向 VM 询问指向对象的所有指针,但这只会让您获得包含 foo 变量的对象,而不是 foo 本身。在 Ruby 中没有真正的方法来引用变量。正如一个答案所提到的,您仍然可以在数据中放置一个标签来引用它来自哪里等,但通常这不是解决大多数问题的好方法。您可以首先使用散列来接收值,或者使用散列传递给您的循环,以便您知道参数名称以进行验证,如 DBR 的答案。

      【讨论】:

        【解决方案11】:

        最接近您问题的真正答案的是使用 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
        

        我从您传递给 each/each_with_index 的块中删除了 return 语句,因为它没有做/没有任何意义。 each 和 each_with_index 都返回它们正在操作的数组。

        这里还有一点值得注意的是块中的作用域:如果你在块之外定义了一个变量,那么它在它里面是可用的。换句话说,您可以直接在块内引用 foo、bar 和 baz。反之则不然:您第一次在块内创建的变量在块外将不可用。

        最后,do/end 语法是多行代码块的首选,但这只是风格问题,尽管它在任何近期的 ruby​​ 代码中都是通用的。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-06-08
          • 2011-04-05
          • 2019-09-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多