【问题标题】:Ruby: Retrieve local variable from classRuby:从类中检索局部变量
【发布时间】:2017-04-19 05:57:48
【问题描述】:

我正在阅读元编程并遇到了这个练习:

http://ruby-metaprogramming.rubylearning.com/html/Exercise_1.html

问题是:

给定这个类的定义:

class A
  def initialize
    @a = 11
    @@a = 22
    a = 33
  end
  @a = 1
  @@a = 2
  a = 3
end

从类外部检索所有变量,输出如下:

1
2
3
11
22
33

现在,动态获取实例和类变量非常简单,甚至是构造函数中的局部变量(它是初始化方法的返回值)。但我对如何获取局部变量 a=3 感到困惑。

据我所知,这是不可能的,因为在第一次读取类定义后,局部变量不再存在。

我完成这项工作的唯一迂回方法是将变量设置为声明类时的“返回”值(因为缺少更好的术语),如下所示:

val = class A
  a = 3
end

puts val # => 3

这是唯一的方法吗?

【问题讨论】:

  • 您可以尝试在班级级别定义def a=(val)。这会捕获它,但不确定如何让它像正常的本地分配一样工作。

标签: ruby


【解决方案1】:

问题似乎可以归结为:鉴于以下情况:

class A
  attr_reader :instance_var
  def initialize
    @instance_var = (@instance_var ||= 0) + 1
    instance_local_var = 33
    puts "instance_local_variables = #{ local_variables }"
    instance_local_var = 33
  end
  class_local_var = 3
  puts "class_local_variables = #{ local_variables }"
  class_local_var = 3
end
  # class_local_variables = [:class_local_var]
  #=> 3

可以确定instance_local_varclass_local_var 的值吗?

确定class_local_var的值

这个问题的答案显然是“否”,因为在end 执行后class_local_var 不再存在(已被标记为垃圾回收)1

A.send(:local_variables)
  #=> []

确定instance_local_var的值

a = A.new
  # instance_local_variables = [:instance_local_var]
  #=> #<A:0x007ff3ea8dbb80 @instance_var=1> 

注意@instance_var #=&gt; 1

A.new 不会返回instance_local_var 的值,但因为该变量在initialize 的最后一行被赋值,所以可以通过再次执行initialize 来获得该值。2

instance_local_var = a.send(:initialize)
  #=> 33

但是有一个问题:

a.instance_var
  #=> 2

第二次执行initialize 导致了不必要的副作用。我对initialize 的定义是人为的,但它强调了这样一个事实,即第二次执行initialize 可能会出现许多不良副作用。

现在让我们获取一个新实例。

b = A.new
  # instance_local_variables = [:instance_local_var]
  #=> #<A:0x007fee0996e7c8 @instance_var=1>

再次,@instance_var=1。对于给定实例调用两次initialize 的副作用的一种可能解决方法是继承A 并使用super

class B < A
  attr_reader :b
  def initialize
    @b = super
  end
end

B.new.b
  #=> 33
a.instance_var
  #=> 1

无法保证使用这种方法可以避免不良副作用(例如,initialize 对于任何实例都可以执行应该只发生一次的数据库操作),但它似乎离开了初始实例 a不受影响。这当然都是假设。

1.必须使用send,因为A.private_methods.include?(:local_variables) #=&gt; true

2。 A.new.send(:initialize) 是必需的,因为 iniitialize 是私有的。

【讨论】:

    【解决方案2】:

    您的问题不清楚。在标题中,您写的是“局部变量”,但在示例中,您只提到了实例变量和类变量。

    至于实例变量,此时可以使用 Object#instance_variables 获取已知实例变量的列表。但是请注意,实例变量是动态创建的,而不是在类声明时。例如,给定类

    class AA; 
      def initialize; @x=1; end; 
      def f; @y=1; end; 
    end
    

    表达式

    AA.new.instance_variables
    

    将返回 [:@x] - :@y 丢失,因为它还不存在。

    您无法自动(即不修改类)检索局部变量。正如 mudasobwa 在他的回答中解释的那样,您必须明确地传回绑定。

    【讨论】:

    • but in the example, you mention only instance- and class-variables. 真的吗? :) a = 33a = 3 是什么,如果不是局部变量?
    • 尽管您的回答可能对其他读者有价值,但绝对不是 OP,因为对他来说,引用:“动态获取实例和类变量非常简单,即使是本地构造函数中的变量。[...] 但我对如何获取局部变量 a=3 感到困惑。”。如此有效,这个答案与问题无关:)
    • 确实!如果这出现在原始问题中并且后来没有添加,我肯定没有看到它!没错,我完全没抓住重点!!
    【解决方案3】:

    可以通过从类定义返回它的绑定来访问类的所有局部变量(详见Binding#local_variables):

    a = class A
      v1 = 3.14
      v2 = 42
      binding
    end
    
    a.local_variable_get(:v1)
    #⇒ 3.14
    a.local_variable_get(:v2)
    #⇒ 42
    

    但主要问题是您为什么要这样做? local 变量旨在保持 local。这就是它应该表现的方式。此外,无法修改原始binding 中的局部变量(返回的是原始绑定的只读副本。)

    【讨论】:

    • 我想知道为什么练习说“你能从 A 类之外获取值吗?”和“编写输出如下的代码:”我想这意味着不更改类定义,这使得任务不可能 AFAIKT。
    • @AndreyDeineko 好吧,通过 NIF 读取堆栈是可能的 :) 这些练习已经过时(2009 年)并且通常看起来根本不是练习的最佳来源。
    • @mudasobwa : 什么是 NIF?在谷歌上没找到。但是 IMO 在从外部查看类时,即没有实际执行方法时,询问可能存在或不存在的局部变量的值是没有意义的。变量本身不存在,求值也没意义。
    • @user1934428 也称为FFI,代表Native Function Interface。是的,尝试从外面发现当地人没有多大意义..
    猜你喜欢
    • 1970-01-01
    • 2016-08-23
    • 2011-12-21
    • 1970-01-01
    • 2021-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多