【问题标题】:Calling variable name of instance from within class从类中调用实例的变量名
【发布时间】:2014-10-03 18:37:44
【问题描述】:

我想知道是否有任何方法可以从 Ruby 中的类本身中调用类实例设置为的变量名。我想做以下事情。

class Playlist
  def display
    puts "the name of this playlist is ___"
  end
end

alternative = Playlist.new 
alternative.display
# => "the name of this playlist is alternative"

我需要帮助填写空白,以便调用 display 将返回我想要的内容。

【问题讨论】:

  • 不是真的,至少没有真正有意义的方法。也就是说,我想不出你真正想要的太多理由。考虑一个名为“新歌”的播放列表,你就会爆炸。 Playlist 应该一个名字,而不是一个。如果由于某种原因您对实现诸如真实播放列表的工作方式之类的东西已经死心了,您可以将播放列表保存在可以具有任意字符串键的地图中。
  • 您写了“the 变量名”,但是是什么使它成为“the”而不是“a”? Ruby 不允许多个变量指向一个对象。并非所有对象都分配给变量。您的问题不清楚。
  • 获取实例化对象的变量名几乎是闻所未闻的。大多数人会使用初始化方法设计 Playlist 类,并在创建播放列表时分配名称,例如 Playlist.new("NameofPlaylist")。即便如此,我对这个问题很感兴趣,我正在深入研究 Ruby 核心,看看这是否可能。只是要知道,你要求完成的方式并不是它的设计方式。

标签: ruby


【解决方案1】:

非常规方法(解决您提出的问题)

好的。为了后代,我将把代码放在这里如何完成你的要求。请记住,这不是该语言的使用方式。但是,如果您进入元编程,这将是非常有用的知识。

class Playlist
  
  def display
    puts "Your playlist name is #{name}"
  end

  private
  def name
    scope = ObjectSpace.each_object(Binding).to_a[-1]
    scope.
      local_variables.
      select {|i| eval(i.to_s, scope) == self}.
      map(&:to_s).delete_if {|i| i== "_"}.first
  end
  
end

alternative = Playlist.new
# => #<Playlist:0x00000002caad08> 
alternative.display
# Your playlist name is alternative

详细信息(工作原理)

好吧,让我解释一下这些部分。 ObjectSpace 是存储所有对象的地方。您可以通过调用 ObjectSpace.count_objects 来查看存在的对象数量。在我看来,最有用的特性是 each_object 方法。使用此方法,您可以迭代许多已创建的任何特定对象。因此,对于播放列表,您可以调用 ObjectSpace.each_object(Playlist) 并获得一个 Enumerable 对象。我们可以通过在末尾附加 .to_a 将其转换为列表。但是此时您将在这样的数组中获取播放列表的实例:[#&lt;Playlist:0x0000000926e540&gt;, #&lt;Playlist:0x000000092f4410&gt;, #&lt;Playlist:0x000000092f7d90&gt;]。如果您想单独访问它们并执行某些操作,这很有用。但这不是您想要做的,因为我们没有这些实例分配给的实例化变量名称。

我们真正想要调用的是 local_variables 方法,我们希望在主范围内(而不是在您的类范围内)调用它。如果您在显示方法中调用 local_variables,您将返回一个空数组 []。但是如果你在创建实例后在主控制台中调用它,你会得到类似[:alternative, :_] 的东西。现在我们在说话!现在存在从类外部获取范围以在其中使用的问题。这很难追踪。通常你可以将 binding 作为参数传入,甚至可以使用 TOPLEVEL_BINDING。但我注意到的一些事情告诉我,它们每个都创建了一个不会再更新的 Binding 实例。这意味着一旦您调用 TOPLEVEL_BINDING,您定义的任何其他内容(例如另一个播放列表)都不会更新,并且不会更新到您的 TOPLEVEL_BINDING.local_variables 列表中。这对我来说是一件可悲的事情。但我发现了解决这个问题的方法。

通过调用ObjectSpace.each_object(Binding).to_a,我们现在有了每个绑定实例的列表。所以我们只需要知道如何获得最新的最新版本。经过试验,我发现最后一个总是最新的。所以我们按[-1] 索引。现在我们可以在其上调用 .local_variables ,我们将始终获得全局范围内的最新实例变量集合。这很棒!现在我们只需要将实例变量与我们所在的当前播放列表匹配。所以我们从全局 local_variables 中选择任何匹配当前实例的变量。我们需要调用 eval 来获取实例,并且使用 eval 我们需要告诉它要运行的范围,所以我们使用select {|i| eval(i.to_s, scope) == self}。从那里我们获取符号并使用 .map(&:to_s) 将它们映射到字符串,最后我们的列表中有一个我们不需要的额外项目。下划线符号是一种 Ruby 技巧,用于获取最后处理的内容。所以我们需要删除它,因为它评估为与我们当前变量实例相同的 id。所以我们做 .delete_if {|i| i== "_"}。最后,它是一个项目列表,我们想要的东西,所以我们用 .first

来挑选它

注意:这种范围选择方法在 Rails 中不起作用。实例化了许多绑定。带有 local_variables 的最后一个和最大的不是最新的。

这经历了许多非常规的方式来完成您所询问的任务。现在,您可能不知道命名播放列表类之类的标准方式,这没关系。一开始没人知道,这是一种后天习得的特质。

命名播放列表的传统方式

这是命名播放列表类的首选方法。

class Playlist

  def initialize(name)
    @name = name
  end
  
  def display
    puts "Your playlist name is #{@name}"
  end

end

list = Playlist.new("Alternative")
list.display
# => "Your playlist name is Alternative"

这是相当直截了当的。最好按照设计使用语言的方式工作。

如果我是你,我会列出一个播放列表项目数组并像这样使用它。

list = []
list << Playlist.new("Alternative")
list << Playlist.new("Rock")
list
# => [#<Playlist:0x000000028a4f60 @name="Alternative">, #<Playlist:0x000000028e4868 @name="Rock">]
list[0].display
# Your playlist name is Alternative
list[1].display
# Your playlist name is Rock

现在您有了一个播放列表列表!甜!

当您进入元编程时,您可能会使用这里非常规方法的许多功能。元编程是代码编写代码的地方。很好玩!

【讨论】:

  • 很好的解决方案(但不是一个很好的答案,因为它不简洁)。但是eval(i.to_s, scope).__id__ == self.__id__可以简化为eval(i.to_s, scope).equal?(self)
  • 运行代码对我有用。是否有特定版本的 Ruby 不会?
  • 没有。如果你有a = "foo"; b = "foo",你会得到什么?
  • 为什么是“foo”,来自:b。但是 == 返回真/假。不是一个或另一个值。
  • @6ftDan 可靠的答案。 2014年我无法欣赏它。奇怪的是,我最近问了几乎完全相同的问题,甚至都不记得了!唯一的区别是我试图在常量声明而不是变量声明上做到这一点。您可以查看问题here。如果您有任何想法如何做到这一点,我很想听听。保证这次我不会让你等上 4 年的复选标记。
【解决方案2】:

这就是为什么这是不可能且没有意义的。

想象一下我添加到你的代码中:

n2_playlist = alternative
n2_playlist.display

n2_playlist 会显示什么? n2_playlist? alternative?我有两个指向同一个对象的引用。它就像一个遥控器,具有相同的编程指令。

为了让事情更有趣,我们假设:

[n2_playlist, alternative].each do |some_arg|
  some_arg.display
end

它应该显示什么? some_arg? n2_playlist? alternative? Source

或者这个:

Playlist.new.display #=> What should it print ? We have no variable pointing to it

使用Playlist.new,您正在创建一个新对象,而alternativen2_playlist 只是对该对象的引用。据我所知,Ruby 对象不知道(也不关心)指向它们的引用。在 Ruby 中,您只能操作对象。变量不是对象。 Source

最好将流派放在initialize 方法中。这样,您将拥有一个可以操作的实际 String 对象。

【讨论】:

    猜你喜欢
    • 2014-12-27
    • 1970-01-01
    • 2013-12-05
    • 1970-01-01
    • 1970-01-01
    • 2022-08-11
    • 2012-08-08
    • 2011-01-06
    • 1970-01-01
    相关资源
    最近更新 更多