从父元类获取“实例”变量

时间:2019-01-07 17:46:40

标签: ruby metaprogramming

使用以下代码,我可以通过哪种方式从@arr访问Child

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)

我也尝试了许多其他方法,但是不需要在这里发布它们。

编辑该问题,因为看来我确实需要更多上下文:

我正在尝试将模块添加到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 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]

此代码应简单地复制即可粘贴。

1 个答案:

答案 0 :(得分:1)

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

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语句外,它与问题中包含的内容相同。

首先,打个头:Kernel#puts-任何东西都会返回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

我们看到在arr的类方法Child调用的getter get中,self等于Child,因此该方法寻找一个@arr的类实例变量Child而不是Parent的类实例变量。由于尚未初始化此类实例变量,因此将返回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的{​​{1}}类方法将调用getter def self.get; self.arr; end,并因此返回Parent::arr的实例变量{{1}的值}。调用的是Parent,但是该方法是从@arr继承的,正在检索的是Child::arr的类实例变量Parent,这很微妙。 ,但重要的是区别。

编辑2

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

@arr

无论其编写方式如何,在父类上调用任一类方法时,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 。因此,第一个将创建类实例变量self

Parent

现在让我们为@arr创建一个类变量。

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

现在让我更改Parent的值。

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"] 

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

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 )现在将此模块放在self.之前。

"superclass.arr == #{superclass.arr.inspect}"

以等于Parent的{​​{1}}触发回调模块方法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::prepended的单例类以base开头,从而添加了该类方法Parent。由于Parent以前没有使用ClassMethodsParent::completion的同名方法,因此具有相同的效果。此外,可以使用Parent回调代替prepend并执行include。也许Parent.singleton_class.include ClassMethods在这里通常用于included(base)可能具有该名称的类方法的情况。 1

现在执行以下操作。

Parent.extend ClassMethods

在以下时间引发异常

prepend

正在执行。等于

Parent

但是当然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}"

1鉴于puts "self.class.superclass.arr == #{Module.arr}" ,在Module前面加上模块只会导致arr的孩子成为Child.ancestors(而不是Parent)模块;也就是说,如果子项在前置之前已经具有方法Parent,则该方法将不会被模块的同名方法所抢占。

相关问题