【问题标题】:Getting local variable names defined inside a method from outside the method从方法外部获取方法内部定义的局部变量名称
【发布时间】:2016-10-12 01:46:22
【问题描述】:

在 Ruby 中是否可以使用元编程从方法外部获取方法内部定义的所有局部变量名称?

def foo
  var = 100
  arr = [1,2]
end

类似foo.local_variables

我只需要检索变量名,而不是它们的值。

我为什么要查找这些变量名:我有一个类,我想为在“#initialize”中初始化的所有实例变量动态生成设置器。

为了做到这一点,在“初始化”中,我使用了类似instance_variables.each do |inst_var_name| define_singleton_method(...) do ...

这很有效:每个实例化对象都将其设置器作为单例方法。但是,我正在寻找一种解决方案,我可以从“initialize”方法之外读取实例变量名称,以便能够为它们使用“#define_method”,并创建常规方法而不是单例方法。

【问题讨论】:

  • 不,这是不可能的。与只要对象存在就存在的实例变量不同,局部变量在方法被调用之前不存在,并且在方法返回后立即停止存在。没有“方法内”和“方法外”;只有“方法正在执行时”和“方法未执行时”。你想解决什么问题?
  • 如果可能的话,ruby 应该在运行时保留多少元数据?他们将占用多少内存?这是不可行的。
  • @Jordan 我不同意。我不是想获取局部变量的值,只是它们的名称。由于可以获得方法的源代码,我想知道是否还有一些方法可以获取方法中定义(或试图定义)的局部变量的名称......有意义吗?跨度>
  • 虽然您的问题的答案是“否”,但如果将p local_variables 行添加到foo,执行foo 将导致显示[:var, :arr]
  • ...虽然解析方法也会让你失败,例如define_methodinstance_eval("def ...")。您还没有告诉我们您要解决什么问题。

标签: ruby scope metaprogramming


【解决方案1】:

正如 Jordan 指出的那样,这些变量只存在短暂的时间,它们只在方法正在执行时才存在,并且在之后立即被丢弃。

如果您想检测这些,您需要在方法内部进行。方法本身不允许这种反射。

【讨论】:

  • 请看我在我的问题下回复@Jordan的评论。
  • 如果您觉得非常勇敢,您可能想尝试转储内部 Ruby P 代码并对其进行解析,但我认为常规 Ruby 根本不支持您的要求。
  • 实际上,在任何体面的编译器(例如 Rubinius、JRuby、IronRuby)中,本示例中的局部变量都将被完全优化掉(整个第一行也是如此)。
  • @JörgWMittag 好一点,因为这些实际上是无用的,可以安全地丢弃。
【解决方案2】:

您可以(重新)解析该方法并检查 S-EXPR 树。请参阅下面的概念证明。您可以使用Method#source_location 获取定义方法的文件,然后读取该文件。我的代码肯定有改进的余地,但应该让你开始。它是一段功能齐全的代码,只需要 ruby​​ 解析器 gem (https://github.com/whitequark/parser)。

require 'parser/current'
node = Parser::CurrentRuby.parse(DATA.read) # DATA is everything that comes after __END__

def find_definition(node, name)
  return node if definition_node?(node, name)
  if node.respond_to?(:children)
    node.children.find do |child|
      find_definition(child, name)
    end
  end
end

def definition_node?(node, name)
  return false if !node.respond_to?(:type)
  node.type == :def && node.children.first == name
end

def collect_lvasgn(node)
  result = []
  return result if !node.respond_to?(:children)

  node.children.each do |child|
    if child.respond_to?(:type) && child.type == :lvasgn
      result << child.children.first
    else
      result += collect_lvasgn(child)
    end
  end

  result
end

definition = find_definition(node, :foo)
puts collect_lvasgn(definition)

__END__

def foo
  var = 100
  arr = [1,2]
  if something
    this = 3 * var
  end
end

def bar
  var = 200
  arr = [3, 4]
end

您介意告诉我们您为什么要查找变量吗?

【讨论】:

  • 您好,我在问题中添加了一些相关信息,谢谢。
  • 感谢您的更新。不确定我是否完全理解您的要求。但这听起来像是一种骇人听闻的做事方式。如果它做起来那么复杂,那么它可能不是很地道(尽管你可以做到)。当涉及到动态创建的东西时,我的方式可能也有缺点。
  • 是的,我知道这感觉太老套了,但这是你面临学习新事物的时刻之一,所以你一直在努力! :) 实际问题不再那么重要了.. 我真的希望有另一种方法来解决它。我可能会发布一个问题来讨论这个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-05-23
  • 2020-10-10
  • 2015-05-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多