请考虑以下脚本:
module Kernel
unless defined?(gem_original_require_2)
alias gem_original_require_2 require
private :gem_original_require_2
end
def require(path)
return gem_original_require_2(path)
end
end
p method(:require) # #<Method: main.require>
p method(:require).owner # Kernel
p method(:require).receiver # main
p method(:require).source_location # ["1.rb", 7]
puts '-' * 10
p Kernel.method(:require) # #<Method: Kernel.require>
p Kernel.method(:require).owner # #<Class:Kernel>
p Kernel.method(:require).receiver # Kernel
p Kernel.method(:require).source_location # nil
puts '-' * 10
p Kernel.method(:gem_original_require) # #<Method: Kernel.gem_original_require(require)>
p Kernel.method(:gem_original_require).owner # Kernel
p Kernel.method(:gem_original_require).receiver # Kernel
p Kernel.method(:gem_original_require).source_location # nil
puts '-' * 10
p Kernel.method(:gem_original_require_2) # #<Method: Kernel.gem_original_require_2(require)>
p Kernel.method(:gem_original_require_2).owner # Kernel
p Kernel.method(:gem_original_require_2).receiver # Kernel
p Kernel.method(:gem_original_require_2).source_location # ["/home/yuri/.rubies/ruby-2.6.3/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb", 34]
我有很多关于输出的问题。为什么Kernel
有时是类,有时是模块?为什么它们有不同的接收器?当方法被调用时receiver
变成self
了吗?
但更重要的是,Kernel.require
和Kernel.gem_original_require
是相同的方法吗?Kernel.require
是否在其他地方被覆盖?如果你能回答剩下的问题,那就太棒了。
让我换一种说法。让我们试着用普通的类和方法重现这个问题。正如other question中所述,在Kernel
上定义一个方法会创建2个方法(示例和单例)。因此:
class MyKernel
# original require
def require; puts 'require'; end
def self.require; puts 'require'; end
# copy original require
alias gem_original_require require
class << self
alias gem_original_require require
end
end
main = MyKernel.new
Kernel
实际上是一个模块,但在这里这并不重要。
p MyKernel.method(:require)
== MyKernel.method(:gem_original_require)
# true
p main.method(:require)
== main.method(:gem_original_require)
# true
p main.method(:require)
== MyKernel.method(:require)
# false
因此,假设要比较方法,你必须通过类或示例访问它们。让我们重写require
:
class MyKernel
# override one of the original require's
def require; puts 'require'; end
end
现在我们有3个require
:
main.require (original)
MyKernel.require
main.require
和2 x 1m 10 n 1x s:
main.gem_original_require
MyKernel.gem_original_require
我们可以将like与like进行比较(示例方法与示例方法,单例方法与单例方法),但是我们不再能够访问原始的main.require
,因此只剩下单例方法,下面的公式仍然成立:
p MyKernel.method(:require)
== MyKernel.method(:gem_original_require)
# true
但不适用于真实的的require
:
p Kernel.method(:require)
== Kernel.method(:gem_original_require)
# false
2条答案
按热度按时间dgtucam11#
我想你会在这里找到部分答案:https://stackoverflow.com/a/57236134/6008847
Rubygems代码替换了
require
的包含版本。当调用
Kernel.require
时,会得到原来的require方法。当调用
require
时,从Rubygems获得方法(接收方将是调用require
的上下文)。gem_original_require
是原始require
方法的别名:https://github.com/ruby/ruby/blob/v2_6_3/lib/rubygems/core_ext/kernel_require.rb#L16lvjbypge2#
为了避免歧义,我将调用单例类拥有的方法,其余的都是示例方法。
(尽管有人可能会说单例方法是相应单例类的示例方法。)
你可以把单例方法称为类方法,但这过于简单化了。对象也可以有单例方法:
示例方法通常通过类/模块的示例调用(例如
[].compact
,compact
由Array
类拥有)。但不一定如此(一个例子是导致我产生这个问题的原因之一).增加混乱的是methods
/instance_methods
方法.A.methods
返回对象A
的单例方法,并且A.new.instance_methods
...不可用。所以通常你想在一个类的示例上调用methods
,在一个类/模块上调用instance_methods
,但是...那要看情况而定。对于那些愿意更好地理解这一点的人,我建议these两个links。现在,正如Gimmy给出的答案所述,
require
是一个全局函数,这意味着它既被定义为Kernel
的私有示例方法,也被定义为Kernel
的单例方法。这意味着基本上可以使用
require
作为示例方法从任何地方调用它(因为几乎所有对象都是从Object
继承的,并且Kernel
包含在Object
中):或者,可以使用
Kernel.require
作为单例方法调用它(方法的另一个示例):反过来,这意味着
rubygems
只创建私有示例方法require
的别名。关于第二部分:
r.method(:m)
返回在调用r.m
时将调用的方法。因此MyKernel.method(:require)
引用MyKernel
类(2)的单例方法。MyKernel.method(:gem_original_require)
引用其别名(4)。在第二条语句中,
Kernel.method(:require)
引用了Kernel
模块的单例方法,但是Kernel.method(:gem_original_require)
引用了Kernel
模块的私有示例方法的别名,也就是说,Kernel
模块本身没有单例方法gem_original_require
,但是由于Kernel
也是一个对象,并且Object
具有私有示例方法gem_original_require
(包含在Kernel
模块中),这就是Kernel.method(:gem_original_require)
返回的内容:要使其足够接近
require
的情况:关于第一部分:
首先,让我介绍一下
ruby
输出对象的方式:在单例类的情况下,输出的形式为:
给定一个单例类
S
,original_object
是一个对象,使得original_object.singleton_class == S
。Method
/UnboundMethod
的示例以如下方式输出:r.method(:m)
返回在执行r.m
时将调用的方法。r
是接收方,m
是方法。根据方法的类型(单例/示例),输出中的receiver
表示不同的内容:例如方法,它是接收器的类,对于单例方法,它是接收器本身。owner
是拥有该方法的类/模块(类/模块o
,使得o.instance_methods(false).include?(:m) == true
):如果所有者是单例类,则显示其原始对象(对象
o
,o.singleton_class == owner
)。当接收者和所有者匹配时,括号中的所有者不重复。
如果方法是别名,则原始方法在括号中指定。
.
代替#
用于单例方法(单例类拥有的方法)。现在输出应该是不言自明的:
示例方法(由
Kernel
模块拥有),接收方是main
(属于Object
类)。单例方法(由
Kernel
模块的单例类拥有),接收者为Kernel
,考虑到拥有者为单例类,所以owner
部分取其原始类(Kernal
),由于得到的拥有者和接收者都是Kernel
,所以名称不重复。这两个是方法
require
的别名,它们是示例方法,也就是说原来的也是示例方法,接收者是Kernel
(属于Module
类),所有者是Kernel
。要回答剩下的问题:
为什么内核有时是类,有时是模块?
#<Class:Kernel>
是ruby
输出Kernel
的单例类的方式。为什么他们有不同的接收器?
receiver是方法被调用的对象。如果receiver是显式指定的(例如
Kernel.require
),它是点之前的对象。在其他情况下,它是self
。在顶层self
是main
对象。当一个方法被调用时,接收者会变成自己吗?
应该是的。
Kernel.require
和Kernel.gem_original_require
的方法是否相同?不,第一个是
Kernel
的单例方法(Kernel
的单例类的示例方法),第二个是Kernel
模块的私有示例方法。是否还有其他地方会覆盖Kernel.require?
据我所知没有。
所以,假设要比较方法,你必须通过类或示例访问它们,我们可以比较相似与相似(示例方法与示例方法,单例与单例)。
它们必须引用同一个方法示例,并且接收方必须匹配:
Kernel.method(:require) != Kernel.method(:gem_original_require)
的问题在于它们属于不同的类/模块(Kernel.singleton_class
和Kernel
),尽管接收器和方法定义匹配。