从父元类获取 'instance' 变量

Getting 'instance' variable from parent metaclass

使用下面的代码,我可以通过什么方式从Child访问@arr

class Parent
    class << self
        def create_singleton_variable
            @arr = [1,2,3]
        end

        def arr
            @arr
        end
    end
end

class Child < Parent
    def get
        puts self.arr
    end
    def self.get 
        puts self.arr
    end
end


p "class method call #{Child.get}"
#=> ➜ ruby child.rb    
#=> "class method call "

c = Child.new
p "instance call #{c.get}"
#=> ➜ ruby child.rb 
#=> Traceback (most recent call last):
#=>        1: from child.rb:24:in `<main>'
#=> child.rb:15:in `get': undefined method `arr' for #<Child:0x00007fe0eb02e7d8> (NoMethodError)

我也尝试过许多其他方法,但觉得没有必要 post 在这里。

编辑问题,因为看来我确实需要更多背景信息:

我正在尝试将一个模块添加到 Thor 框架中。然后我想访问 this bit of code

module ThorExtensions
  module Thor
    module CompletionGeneration
      def self.prepended(base)
        base.singleton_class.prepend(ClassMethods)
      end

      module ClassMethods
        def completion
          puts "Start Completion"
          p self
          p self.superclass
          p self.class.superclass.subcommands
          puts "End Completion"
        end
      end
    end
  end
end

结果

Start Completion
Debug
Thor
bundler: failed to load command: exe/pt (exe/pt)
NoMethodError: undefined method `subcommands' for Module:Class
  /Users/tyler.thrailkill/Documents/code/backend/pt-cli/lib/thor_extensions/completion_generation.rb:13:in `completion'
  /Users/tyler.thrailkill/Documents/code/backend/pt-cli/lib/debug/debug.rb:24:in `<class:Debug>'
  /Users/tyler.thrailkill/Documents/code/backend/pt-cli/lib/debug/debug.rb:4:in `<top (required)>'
  /Users/tyler.thrailkill/Documents/code/backend/pt-cli/lib/pt.rb:5:in `require'
  /Users/tyler.thrailkill/Documents/code/backend/pt-cli/lib/pt.rb:5:in `<top (required)>'
  exe/pt:13:in `require'
  exe/pt:13:in `<top (required)>'

这当然不是我想要的。看来我的问题可能与前置有关?

编辑 2

我似乎在解释我的前置问题方面做得很糟糕。这是一个显示我的问题的完整示例。我相信这是由于在 class 之前添加某些内容实际上会在调用堆栈中创建另一个 Class ,而该调用堆栈首先被调用。我希望我实际上仍然能够以某种方式访问​​此方法。

class Parent
  class << self
    def create_singleton_variable
      @arr = [1,2,3]
      puts "arr is initialized #{@arr}"
    end
    # ... lots of code here. 
    def arr
      puts "arr is #{@arr.inspect}"
      @arr
    end
  end
end

module CompletionGeneration
  def self.prepended(base)
    base.singleton_class.prepend(ClassMethods)
  end

  module ClassMethods
    def completion 
      puts "self.superclass.arr == #{self.superclass.arr.inspect}" # unable to access superclass arr
      puts "self.class.superclass.arr == #{self.class.superclass.arr}" # likewise, unable to access Child's metaclass superclass 
    rescue Exception => e
      # do nothing, this is just so you can see arr is actually initialized in the context of the Child
      p e
    end
  end
end

Parent.prepend CompletionGeneration

class Child < Parent
  create_singleton_variable
  completion
  arr
end

Child.new

输出结果

➜ ruby child.rb
arr is initialized [1, 2, 3]
arr is nil
self.superclass.arr == nil
#<NoMethodError: undefined method `arr' for Module:Class>
arr is [1, 2, 3]

此代码应按原样简单复制和粘贴。

这是您的代码,稍作修改。

class Parent
  def self.create_singleton_variable
    @arr = [1,2,3]
  end 
  def self.arr
    puts "self = #{self} in the getter for @arr"
    @arr
  end
end

class Child < Parent
  def get
    puts self.arr
  end
  def self.get 
    puts self.arr
  end
end

我用更常规的方式写了Parent。除了增加了puts语句外,等同于问题中的

首先,一个 head-slapper:Kernel#puts-任何 returns nil。您需要从两种方法中删除 puts

class Child < Parent
  def get
    self.arr
  end
  def self.get 
    self.arr
  end
end

Parent.create_singleton_variable
  #=> [1, 2, 3] 
Child.get.nil?
self = Child in the getter for @arr
  #=> true

我们看到在 getter arr 中,由 Child 的 class 方法调用 getself 等于 Child,因此该方法查找 Child 而不是 Parent 的 class 实例变量 @arr。由于尚未初始化此类实例变量,因此返回 nil

您需要以下内容。

class Parent
  class << self
    def create_singleton_variable
      @arr = [1,2,3]
    end
    def arr
      puts "self = #{self} in the getter for @arr"
      @arr
    end
  end
end

class Child < Parent
  def get
    self.class.superclass.arr
  end
  def self.get 
    superclass.arr
  end
end

与问题中给出的关键区别在于 Class#superclass 将范围(即 self)更改为 Parent

我们看到得到了想要的结果。

Child.get
self = Parent in the getter for @arr
  #=> [1, 2, 3] 

Child.new.class.superclass.arr
self = Parent in the getter for @arr
  #=> [1, 2, 3] 

一个常见的误解是 Child class 方法定义 def self.get; self.arr; end 调用 getter Parent::arr,因此 returns 值Parent 的实例变量 @arr。调用的是 Child::arr,但是,该方法继承自 Parent,并且正在调用的是 Child 的 class 实例变量 @arr找回,一个微妙但重要的区别。

编辑 2

第一个观察结果是 Parent 可以用更传统(且完全等效)的方式编写。

class Parent
  def self.create_singleton_variable
    @arr = [1,2,3]
    puts "arr is initialized #{@arr}"
  end

  def self.arr
    puts "arr is #{@arr.inspect}"
    @arr
  end
end

不管它怎么写,当 parent 上的任何一个 class 方法被调用时,self 将等于 Parent。因此,第一个将创建 class 个实例变量 @arr.

Parent.methods(false)
  #=> [:create_singleton_variable, :arr] 
Parent.instance_variables
  #=> []
Parent.ancestors
  #=> [Parent, Object, Kernel, BasicObject] 

现在让我们为 Parent 创建一个 class 变量。

Parent.create_singleton_variable
  # arr is initialized [1, 2, 3]
Parent.instance_variables
  #=> [:@arr]

现在让我更改 @arr 的值。

Parent.instance_variable_set(:@arr, ['dog', 'cat'])
  #=> ["dog", "cat"]
Parent.arr
  # arr is ["dog", "cat"]
  #=> ["dog", "cat"] 

接下来,创建 class Child,但还不要添加模块。

class Child < Parent
  create_singleton_variable
  arr
end
arr is initialized [1, 2, 3]
arr is [1, 2, 3]

Child.ancestors
  #=> [Child, Parent, Object, Kernel, BasicObject]
Child.instance_variables
  #=> [:@arr] 
Child.instance_variable_get(:@arr)
  #=> [1, 2, 3] 

没有惊喜。接下来加载模块。

module CompletionGeneration
  def self.prepended(base)
    base.singleton_class.prepend(ClassMethods)
  end

  module ClassMethods
    def completion
      puts "self=#{self}"
      puts "superclass=#{superclass}"
      puts "self.class=#{self.class}"
      puts "self.class.superclass == #{self.class.superclass}" 
      puts "superclass.arr == #{superclass.arr.inspect}"
      puts "self.class.superclass.arr == #{self.class.superclass.arr}" 
      rescue Exception => e
        # do nothing, this is just so you can see arr is actually
        # initialized in the context of the Child
        puts "Exception => e=#{e}"
    end
  end
end

(注意 "superclass.arr == #{superclass.arr.inspect}" 中不需要 self.)现在将此模块添加到 Parent

Parent.prepend CompletionGeneration

Parent.ancestors
  #=> [CompletionGeneration, Parent, Object, Kernel, BasicObject] 
Parent.methods.include?(:completion)
  #=> true 
Child.ancestors
  #=> [Child, CompletionGeneration, Parent, Object, Kernel, BasicObject] 
Child.methods.include?(:completion)
  #=> true 

回调模块方法 CompletionGeneration::prependedbase 等于 Parent 时触发,导致 Parent 的单例 class 前置 ClassMethods,从而添加 class 方法 Parent::completion。由于 Parent 以前没有使用该名称的方法,因此使用 prependinclude 会产生相同的效果。此外,可以使用 included(base) 回调代替 Parent.singleton_class.include ClassMethods,并执行 Parent.extend ClassMethods。也许 prepend 在这里用于一般情况,其中 Parent 可能具有该名称的 class 方法。1

现在执行以下命令。

Child.completion
self=Child
superclass=Parent
self.class=Class
self.class.superclass == Module
arr is ["dog", "cat"]
superclass.arr == ["dog", "cat"]
Exception => e=undefined method `arr' for Module:Class

异常发生时

puts "self.class.superclass.arr == #{self.class.superclass.arr}"

正在执行。因为这相当于

puts "self.class.superclass.arr == #{Module.arr}"

当然 Module 没有模块方法 arr

1 鉴于 Child.ancestors,在 Parent 前面加上模块只会导致 Parent 的 children 到 include (而不是 prepend)模块;也就是说,如果 child 在前置之前已经有一个方法 completion,该方法将不会被模块的同名方法抢占。