ruby Kernel.require和Kernel.gem_original_require之间有什么区别?

tcomlyy6  于 2023-01-12  发布在  Ruby
关注(0)|答案(2)|浏览(208)

请考虑以下脚本:

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是否在其他地方被覆盖?如果你能回答剩下的问题,那就太棒了。
让我换一种说法。让我们试着用普通的类和方法重现这个问题。正如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
dgtucam1

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#L16

lvjbypge

lvjbypge2#

为了避免歧义,我将调用单例类拥有的方法,其余的都是示例方法。

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...不可用。所以通常你想在一个类的示例上调用methods,在一个类/模块上调用instance_methods,但是...那要看情况而定。对于那些愿意更好地理解这一点的人,我建议these两个links
现在,正如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

如果所有者是单例类,则显示其原始对象(对象oo.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,考虑到拥有者为单例类,所以owner部分取其原始类(Kernal),由于得到的拥有者和接收者都是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的单例类的方式。
为什么他们有不同的接收器?
receiver是方法被调用的对象。如果receiver是显式指定的(例如Kernel.require),它是点之前的对象。在其他情况下,它是self。在顶层selfmain对象。
当一个方法被调用时,接收者会变成自己吗?
应该是的。
Kernel.requireKernel.gem_original_require的方法是否相同?
不,第一个是Kernel的单例方法(Kernel的单例类的示例方法),第二个是Kernel模块的私有示例方法。
是否还有其他地方会覆盖Kernel.require?
据我所知没有。

所以,假设要比较方法,你必须通过类或示例访问它们,我们可以比较相似与相似(示例方法与示例方法,单例与单例)。
它们必须引用同一个方法示例,并且接收方必须匹配:

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

相关问题