在Ruby中按名称以编程方式访问变量

在Ruby中按名称以编程方式访问变量

Access variables programmatically by name in Ruby

我不确定在Ruby中是否可行,但是希望有一种简单的方法。 我想声明一个变量,然后找出该变量的名称。 也就是说,对于这个简单的代码段:

1
foo = ["goo","baz"]

我如何找回数组的名称(在这里为" foo")? 如果确实可行,这是否适用于任何变量(例如,标量,哈希等)?

编辑:这是我基本上想做的。 我正在编写一个SOAP服务器,该服务器使用三个重要变量包装了一个类,验证代码本质上是这样的:

1
2
3
4
5
6
  [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的答案)


如果您解决问题了怎么办?与其尝试从变量中获取名称,不如从名称中获取变量:

1
2
3
4
5
6
7
["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"以保持评估从引发异常...


您需要重新设计解决方案。即使可以(不能),问题也没有合理的答案。

想象一个get_name方法。

1
2
a = 1
get_name(a)

每个人都可能同意这应该返回" a"

1
2
b = a
get_name(b)

它应该返回" b"或" a",还是包含两者的数组?

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

它应该返回" arg"," b"还是" a"?

1
2
3
4
def do_stuff( arg )
  get_name(arg)
do
do_stuff(b)

它应该返回'arg','b'或'a',还是所有它们的数组?即使它确实返回了数组,其顺序也将如何,我怎么知道如何解释结果?

以上所有问题的答案是"这取决于我当时想要的特定事物。"我不确定您如何解决Ruby的问题。


看来您正在尝试解决一个容易解决的问题。

为什么不将数据仅存储在哈希中?如果你这样做..

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

..然后完全变得微不足道,以获取'foo'名称。

就是说,您尚未对该问题提供任何背景信息,因此可能有您不能执行此操作的原因。

[编辑]澄清之后,我看到了问题,但我不认为这是问题所在。使用[foo,bar,bla],就相当于说['content 1', 'content 2', 'etc']。实际的变量名称是(或者应该是)完全不相关的。如果变量的名称很重要,那就正是为什么存在哈希的原因。

问题不在于遍历[foo,bar]等,这是SOAP服务器如何重现数据和/或尝试使用它的根本问题。

我想说的解决方案是使SOAP服务器返回哈希值,或者因为您知道总会有三个元素,所以您不能做类似的事情。

1
2
3
4
5
6
{"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

好的,它也可以在实例方法中工作,并且根据您的特定要求(您在注释中提出的要求),您可以执行以下操作:

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

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


好问题。我完全理解你的动机。让我首先指出,存在某些特殊对象,在某些情况下,它们对已分配给变量的变量有所了解。这些特殊对象是例如。 Module实例,Class实例和Struct实例:

1
2
Dog = Class.new
Dog.name # Dog

要注意的是,这仅在执行赋值的变量为常数时有效。 (我们都知道Ruby常数不过是情感敏感的变量。)因此:

1
2
3
4
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"

知道对象被分配了哪些变量的对象的这种行为通常称为常量魔术(因为它仅限于常量)。但是,这种高度期望的恒定魔术仅适用于某些对象:

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

幸运的是,我写了一个gem y_support/name_magic,可以为您解决这个问题:

1
2
3
4
5
6
 # first, gem install y_support
require 'y_support/name_magic'

class Cat
  include NameMagic
end

实际上,这仅适用于常量(即,以大写字母开头的变量)并不是很大的限制。实际上,它使您可以随意命名或不随意命名对象:

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

不幸的是,这不适用于数组文字,因为它们是在内部构造而未使用NameMagic所依赖的#new方法的。因此,要实现您想要的目标,您将必须子类Array

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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挂钩。


Kernel::local_variables,但是我不确定这是否适用于方法的本地var,而且我不知道您可以通过操作来实现自己想要的效果。


在joshmsmoore的基础上,可能会执行以下操作:

1
2
3
4
5
6
7
# 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

我不知道以任何方式获取局部变量名称。但是,您可以使用instance_variables方法,这将返回对象中所有实例变量名称的数组。

简单的电话:

1
object.instance_variables

要么

1
self.instance_variables

获取所有实例变量名称的数组。


对于您的问题,最接近实际答案的方法是使用Enumerable方法each_with_index而不是每个方法,因此:

1
2
3
4
5
6
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_with_index都返回它们在其上操作的数组。

块中还有一些关于范围的注意事项:如果您在块外定义了变量,则该变量将在其中可用。换句话说,您可以直接在块内部引用foo,bar和baz。相反,事实并非如此:您在块内首次创建的变量将无法在块外使用。

最后,do / end语法是多行块的首选,但这只是样式问题,尽管它在任何最近年份的红宝石代码中都是通用的。


Foo只是保存指向数据的指针的位置。数据不知道它指向什么。在Smalltalk系统中,您可以要求VM提供指向对象的所有指针,但这只会使您获得包含foo变量的对象,而不是foo本身。在Ruby中没有真正的方法来引用可用商品。就像一个答案提到的那样,您可以在数据中放置一个标记,以引用它的来源,但通常这并不是解决大多数问题的好方法。您可以首先使用散列来接收值,也可以使用散列来传递给循环,这样您就可以像DBR的答案一样知道用于验证目的的参数名称。


不能,您需要返回到绘图板上并重新设计您的解决方案。


推荐阅读