从父元类获取 '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 方法调用 get
,self
等于 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::prepended
在 base
等于 Parent
时触发,导致 Parent
的单例 class 前置 ClassMethods
,从而添加 class 方法 Parent::completion
。由于 Parent
以前没有使用该名称的方法,因此使用 prepend
或 include
会产生相同的效果。此外,可以使用 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
,该方法将不会被模块的同名方法抢占。
使用下面的代码,我可以通过什么方式从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 方法调用 get
,self
等于 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::prepended
在 base
等于 Parent
时触发,导致 Parent
的单例 class 前置 ClassMethods
,从而添加 class 方法 Parent::completion
。由于 Parent
以前没有使用该名称的方法,因此使用 prepend
或 include
会产生相同的效果。此外,可以使用 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
,该方法将不会被模块的同名方法抢占。