代码之家  ›  专栏  ›  技术社区  ›  snowe

从父元类获取“instance”变量

  •  1
  • snowe  · 技术社区  · 6 年前

    用下面的代码,我可以用什么方式访问 @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

    我似乎做了一件很糟糕的工作,用预先准备来解释我的问题。下面是一个充分发挥作用的例子,展示了我的问题。我认为这是因为在调用堆栈中预先为类准备的内容实际上是如何创建另一个名为First的类。我希望我仍然能够以某种方式访问这个方法。

    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 回复  |  直到 6 年前
        1
  •  1
  •   Cary Swoveland    6 年前

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

    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 . 你需要移除 从两种方法:

    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 类方法 get , self 等于 孩子 ,因此该方法查找类实例变量 @arr 属于 孩子 不是 起源 . 由于没有初始化此类实例变量, 返回。

    您需要以下内容。

    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 更改范围(即, 自己 起源 .

    我们看到期望的结果已经得到了。

    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] 
    

    一个常见的误解是 孩子 类方法已定义 def self.get; self.arr; end 调用getter Parent::arr ,因此返回 起源 的实例变量 阿尔 . 它是 Child::arr 但是,该方法是从 起源 它就是 孩子 的类实例变量 阿尔 这是一个微妙但重要的区别。

    编辑2

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

    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.methods(false)
      #=> [:create_singleton_variable, :arr] 
    Parent.instance_variables
      #=> []
    Parent.ancestors
      #=> [Parent, Object, Kernel, BasicObject] 
    

    现在让我们为 起源 .

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

    现在让我改变 阿尔 .

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

    接下来,创建类 孩子 ,但不要预先准备模块。

    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.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 等于 起源 造成 起源 的singleton类 ClassMethods ,从而添加类方法 Parent::completion . 自从 起源 以前没有使用该名称的方法 prepend include 会有同样的效果。更进一步,而不是 Parent.singleton_class.include ClassMethods ,可以使用 included(base) 改为回调,并执行 Parent.extend ClassMethods . 也许 预置 这里是一个普通的案例 起源 可能具有该名称的类方法。

    现在执行以下操作。

    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 准备 起源 模块的唯一原因 起源 孩子们 包括 (而不是 预置 )模块;也就是说,如果子级已经有了方法 completion 在预处理之前,该方法不会被模块的方法以相同的名称抢先。