Kernel.require和Kernel.gem_original_require有什么区别?



请考虑以下脚本:

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.requireKernel.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_requires:

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

实例方法通常通过类/模块的实例调用(例如[].compactcompactArray类所有)。但不一定如此(一个例子是导致我提出这个问题的原因之一)。增加混乱的是methods/instance_methods方法。A.methods返回对象A的单例方法,A.new.instance_methods...不可用。所以通常你想在类的实例上调用methodsinstance_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>

给定一个单例类Soriginal_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.在顶层selfmain对象。

当一个方法被调用时,接收者会变成自我吗?

据说是的。

Kernel.requireKernel.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_classKernel),尽管接收器和方法定义匹配。