请考虑以下脚本:
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
被覆盖?如果你能回答其余的问题,那就太棒了。
让我换一种说法。让我们尝试使用普通类和方法重现此问题。如另一个问题所述,在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
和 2gem_original_require
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
我想你会在这里找到部分答案: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#L16
为了避免歧义,我将调用单例类拥有的方法 单例方法,其余的都是实例方法。
class A
def m; end # instance method
def self.m; end # singleton method
end
(尽管有人可能会说单例方法是相应单例类的实例方法。
有人可能会将单例方法称为类方法,但那将过于简单化。对象也可以具有单一实例方法:
a = A.new
def a.m2; end
p a.singleton_methods(false).include? :m2 # true
p a.singleton_class.instance_methods(false).include? :m2 # true
实例方法通常通过类/模块的实例调用(例如[].compact
,compact
归Array
类所有)。但不一定如此(一个例子是导致我提出这个问题的原因之一)。增加混乱的是methods
/instance_methods
方法。A.methods
返回对象A
的单例方法,A.new.instance_methods
...不可用。所以通常你想在类的实例上调用methods
,instance_methods
在类/模块上调用,但是......这要看情况。对于那些愿意更好地理解这一点的人,我建议这两个链接。
现在,正如Gimmy建议的答案中所述,require
是一个全局函数。这意味着它被定义为Kernel
的私有实例方法和Kernel
的单例方法。
$ ruby --disable-gems -e 'p [
Kernel.private_instance_methods(false).include?(:require),
Kernel.singleton_class.instance_methods(false).include?(:require),
Kernel.instance_method(:require) == Kernel.singleton_class.instance_method(:require)
]'
[true, true, false]
这意味着您基本上可以使用require
作为实例方法从任何地方调用它(因为几乎所有的对象都是从Object
继承的,并且Kernel
包含在Object
中):
require 'time'
或者,您可以使用Kernel.require
将其作为单一实例方法调用它(方法的另一个实例):
Kernel.require 'time'
反过来,这意味着rubygems
仅创建私有实例方法的别名require
。
关于第二部分:
class MyKernel
# original require
def require; puts 'require'; end # (1)
def self.require; puts 'require'; end # (2)
# copy original require
alias gem_original_require require # (3)
class << self
alias gem_original_require require # (4)
end
end
main = MyKernel.new
class MyKernel
# override one of the original require's
def require; puts 'require'; end
end
p MyKernel.method(:require)
== MyKernel.method(:gem_original_require)
# true
p Kernel.method(:require)
== Kernel.method(:gem_original_require)
# false
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)
返回的内容:
p
Kernel.singleton_class
.instance_methods(false).include?(:gem_original_require),
# false
Kernel.singleton_class
.private_instance_methods(false).include?(:gem_original_require),
# false
Kernel.class.ancestors,
# [Module, Object, Kernel, BasicObject]
Kernel.private_instance_methods(false).include?(:gem_original_require)
# true
为了使它足够接近require
发生的事情:
module Kernel
def m; end
private :m
def self.m; end
alias malias m
end
p Kernel.method(:m) == Kernel.method(:malias) # false
关于第一部分:
def pmethod n, m
puts n + ': ' + m.inspect
puts ' owner: ' + m.owner.inspect
puts ' receiver: ' + m.receiver.inspect
puts ' source_location: ' + m.source_location.inspect
end
def hr
puts '-' * 3
end
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
pmethod 'require', method(:require)
pmethod 'Kernel.require', Kernel.method(:require)
pmethod 'Kernel.gem_original_require', Kernel.method(:gem_original_require)
pmethod 'Kernel.gem_original_require_2', Kernel.method(:gem_original_require_2)
首先让我说几句关于ruby
输出对象的方式:
p Object #=> Object
p Object.new #=> #<Object:0x000055572f38d2a0>
p Kernel #=> Kernel
对于单例类,输出的形式为:
#<Class:original_object>
给定一个单例类S
,original_object
是一个对象,使得original_object.singleton_class == S
。
p Kernel.singleton_class #=> #<Class:Kernel>
p Object.new.singleton_class #=> #<Class:#<Object:0x000055cad6695428>>
Method
/UnboundMethod
的实例以这种方式输出:
#<Method: receiver(owner)#method(original_method)>
r.method(:m)
返回在执行r.m
时将调用的方法。r
是接收者,m
是方法。根据方法的类型(单例/实例),输出中的receiver
意味着不同的东西:例如方法它是接收器的类,对于单例方法,接收器本身。owner
是拥有该方法的类/模块(类/模块o
,以便o.instance_methods(false).include?(:m) == true
):
class A
def m; end # owner is A
# A.instance_methods(false).include?(:m) == true
def self.m; end # owner is the singleton class of A
# A.singleton_class
# .instance_methods(false).include?(:m) == true
end
如果所有者是单一实例类,则显示其原始对象(o
对象,以便o.singleton_class == owner
)。
当接收方和所有者匹配时,括号中不会重复所有者。
如果方法是别名,则在括号中指定原始方法。
.
用于代替单例方法(单例类拥有的方法)的#
。
现在输出应该是不言自明的:
require: #<Method: Object(Kernel)#require>
owner: Kernel
receiver: main
source_location: ["a.rb", 17]
一个实例方法(由Kernel
模块拥有),接收器main
(Object
类)。
Kernel.require: #<Method: Kernel.require>
owner: #<Class:Kernel>
receiver: Kernel
source_location: nil
单例方法(由Kernel
模块的单例类拥有),接收器Kernel
。考虑到所有者是一个单例类,其原始类(Kernal
)被取为owner
部分。并且由于生成的所有者和接收者都是Kernel
,因此名称不重复。
Kernel.gem_original_require: #<Method: Module(Kernel)#gem_original_require(require)>
owner: Kernel
receiver: Kernel
source_location: nil
Kernel.gem_original_require_2: #<Method: Module(Kernel)#gem_original_require_2(require)>
owner: Kernel
receiver: Kernel
source_location: ["/usr/local/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb", 34]
这两个是方法require
的别名。它们是实例方法,这意味着原始方法也是实例方法。接收器Kernel
(Module
类)。业主Kernel
。
要回答问题的其余部分:
为什么内核有时是一个类,有时是一个模块?
#<Class:Kernel>
是ruby
输出单例类Kernel
的方式。
为什么他们有不同的接收器?
接收器是要调用其方法的对象。如果明确指定了接收器(例如Kernel.require
),它是点之前的对象。在其他情况下,它是self
.在顶层self
是main
对象。
当一个方法被调用时,接收者会变成自我吗?
据说是的。
Kernel.require
和Kernel.gem_original_require
是同一种方法吗?
不,第一个是Kernel
的单例方法(Kernel
的单例类的实例方法),第二个是Kernel
模块的私有实例方法。
有没有另一个地方 Kernel.require 被覆盖?
不是我知道的。
因此,据说要比较方法,您必须通过类或实例访问它们。 我们可以比较 like 和 like(实例方法与实例方法,单例与单例)。
它们必须引用方法的同一实例,并且接收器必须匹配:
class A
def m; end
alias ma m
end
class B < A; end
a = A.new
b = B.new
p a.method(:m) == a.method(:ma) #=> true
p a.method(:m) == b.method(:m) #=> false
# receivers do not match
p A.instance_method(:m) == B.instance_method(:m) #=> false
# receivers do not match
p a.method(:m) == A.instance_method(:m) #=> false
# #<Method:...> can't be equal #<UnboundMethod:...>
Kernel.method(:require) != Kernel.method(:gem_original_require)
的问题在于它们属于不同的类/模块(Kernel.singleton_class
和Kernel
),尽管接收器和方法定义匹配。