Ruby kernel 中的 puts 方法是模块实例方法还是模块方法

runup · 2015年12月17日 · 最后由 Evilcrow 回复于 2018年02月09日 · 2858 次阅读

问题:kernel中的puts方法是模块实例方法还是模块方法(是否存在模块实例方法和模块方法这种区别)?Kernel中有下面的一句话:

The Kernel instance methods are documented in class Object while the module methods are documented here.

是否说明本篇文档罗列的方法都是模块方法,比如说该文档中的puts方法,可以通过kernel.puts实现的,但是在文档左侧的列表中出现的标示符号都是"#"符号,这应该是模块实例方法的标示符,如果是模块方法,应该使用“::”,见文档所示。官方文档都不会出错,能不能帮忙指出我的理解错误。

共收到 21 条回复

Ruby中有一些方法(如print)可以随时随地进行调用。看起来就像所有的对象都有print方法一样。这是因为这些方法实际上都是Kernel模块的私有实例方法:

Kernel.private_instance_methods.grep(/^pr/) # => [:printf, :print, :proc]

这里的秘密在于Object类包含了Kernel模块,因此Kernel就进入了每个对象的祖先链。于是,无论哪个对象都可以随意调用Kernel模块的方法。这使得print看起来就像一个关键字,其实它只是一个方法而已。

-----摘自《Ruby 元编程第2版》

我自己有点钻牛角尖了,不多还是想把细节搞清楚,看了原书的内容。下面有几个小问题。 问题1:任何对象都可以调用Kernel模块的方法,但是下面的obj对象调用会报错。

class KernelDemo
end

obj = KernelDemo.new
obj.puts("this is the Kernel method")  #报错private method puts

问题2:我构造了如下的代码,在模块中定义private method, 并且include到类中,并且通过子类继承该类(类似于Kernel、Object和普通类的关系),但是在子类中调用private method会报错。

module KernelMdoule
  def cc
    puts "this is the private method"
  end
  private : cc 
end

class FatherClass
  include KernelModule
end

class SonClass < FatherClass
  cc
end

问题3:官方文档 中所有的方法都是标为public instance method,但是通过在irb中输入如下Kernel.private_instance_methods可以得知本页中的所有方法都是private instance method.而

Kernel.public_instance_methods.grep(/send/) #=>[:send, :public_send]

可以得知send方法是Kernel的公有实例方法,因为Kernel被引入到类object中,因此可以像普通公开实例方法一样被对象调用,而puts 是私有实例方法,可以像关键词一样调用。如果是这样,那么文档中的表述是否有问题:

The Kernel instance methods are documented in class Object while the module methods are documented here. These methods are called without a receiver and thus can be called in functional form:

#1楼 @qinfanpeng 谢谢您的解答,能不能帮我看看我的理解和疑问,非常感谢。

#3楼 @runup 问题1: 私有方法不可以显式调用. 问题2: 子类这种写法相当于执行父类的self.cc也就是Father.cc. 问题3还没看.

#2楼 @runup 第一,你的代码里到处都是 TYPO。 第二, 私有方法你只能在这个类或者其子类的实例方法里调用,而且不能指定调用对象。

@runup

我试着解答一下:

问题1:

obj.puts("this is the Kernel method") #报错private method puts

puts 是 private instance method。private 的意思就是不能显示地指定 receiver object。你在这里显式指定 obj 为 receiver,当然会报错啦。

问题2: 你这里笔误了吧。class SonClass < KernelClass 应该是 class SonClass < FatherClass 才对。 而且,你调用 cc 方法的时候,receiver 是 SonClass,而不是 SonClass 的实例,当然获取不到实例方法了。

问题3: Ruby 文档的确有问题

#3楼 @runup 其他两个问题,其他小伙伴解释得很清楚了;第三个问题,你给的链接里都没内容,这得基于上下文来说。

#6楼 @fighterleslie 非常感谢。问题二中代码的错误已经进行了修改。给出问题二的代码是为了和Kernel中的puts方法做比较,比如如下的代码:

puts "method one"  #当前对象是main

class KernelMethod
  puts "method two"  #当前对象是class KernelMethod

  def method_three
    puts "method three"  #当前对象是obj
  end
end

obj = KernelMethod.new
obj.method_threee

结合上面的小伙伴的解释,补充了私有方法不能显式调用的知识,但是在类中进行调用puts方法(puts "method two"),与实例方法(puts是private instance method)只能被实例化对象调用相违背。在该例中,puts "method two"方法可以实现,但是在我给出的问题2中类似的方法是不能实现的。 我应该存在理解上的误区或者知识漏洞,感谢指正。

#7楼 @qinfanpeng 链接已经可以访问了

#8楼 @runup

但是在类中进行调用puts方法(puts "method two"),与实例方法(puts是private instance method)只能被实例化对象调用相违背。

Good question. 你的疑惑是为什么在 KernelMethod 这个类上可以调用 puts, 因为 KernelMethod 这个类是 Class 类的实例对象 (Ruby 中,一切值都是对象。)

KernelMethod.class  # => Class
KernelMethod.instance_of?(Class)  # => true

The Kernel module is included by class Object, so its methods are available in every Ruby object. The Kernel instance methods are documented in class Object while the module methods are documented here. These methods are called without a receiver and thus can be called in functional form:

你是感觉其中的the module methods较为迷惑对吧?我猜测它说的是module_function, 若这样的话,它干了两件事情:首先,把紧跟其后的方法都标注成了私有方法; 其次,让紧跟其后的方法可以单例方式访问。

例如:

print 'Hello world'  # 毫无疑问,这样可以
Kernel.print 'Hello world'  # 这也可以。但如果 print 只是Kernel里一个普通的 private 方法的话,是不能这样玩的。

#10楼 @fighterleslie 感谢解答,您的解答我也考虑过,同理参考问题2中的例子:

module KernelMdoule
  def cc
    puts "this is the private method"
  end
  private : cc 
end

class FatherClass
  include KernelMdoule
end

class SonClass < FatherClass
  cc
end

这样的写法会报错,原因为undefined local variable or method,您的解释为:

“你调用 cc 方法的时候,receiver  SonClass,而不是 SonClass 的实例,当然获取不到实例方法了

但是其实SonClass也是一个对象,是Class的实例化对象:

puts SonClass.class  #Class
puts SonClass.instance_of?(Class) #true

在我的理解里面,问题二中的代码

class SonClass < FatherClass
  cc
end

class SonClass < FatherClass
  puts this is an private instance method 
end

的实现形式是一致的,我在问题二的代码中也实现在module中构造私有实例方法,并且include到父类中,并且在子类中直接进行调用,这和Object、Kernel以及其他Class的实例化对象(即普通类)实现方式类似,但是实现的结果却是有错的,这是我的困惑所在,也是我的理解上的误区,能不能帮忙指正一下,感谢。

#13楼 @runup

简单的说,虽然 putscc 都是 private method,但是 puts 是定义在 Kernel 模块中,cc 方法是定义在 KernelModule 模块中。Kernel 是一个特殊的模块,它和你自己定义的 KernelModule 模块有一些行为上的不同。

详细地说,这涉及两方面的知识:

  1. Ruby 的对象体系
  2. 方法的查询机制
module KernelModule
  def cc
    puts "this is the private method"
  end
  private : cc 
end

class FatherClass
  include KernelMdoule
end

class SonClass < FatherClass
  cc
end

SonClass.ancestors  # => [SonClass, FatherClass, KernelModule, Object, Kernel, BasicObject]
SonClass.class.ancestors # => [Class, Module, Object, Kernel, BasicObject]

Kernel 模块已经在 Object 模块中 include,而几乎所有的 Ruby 类都是直接或间接地继承自 Object,所以 Kernel 模块会出现在所有类的 ancestors 列表中。当在 receiver 上调用 cc 方法时,Ruby 解释器会沿 receiver.class.ancestors 逐级查找是否有 cc 的定义,如果找到,会调用 cc 方法,同时不再向上查找;如果遍历 ancestors 而没有找到,则会在 receiver 上调用 method_missing 方法,一般情况下会抛出 NoMethodError 错误。

#14楼 @fighterleslie 终于把这个知识点搞的有点明白了,感激,作为周星驰的粉丝,很喜欢你的头像。

#12楼 @qinfanpeng 认真看了module_function的方法解释,实现了您提到的两个功能,一是作为private instance method方法被调用,二是可以直接用模块名进行调用。但是send方法为何可以被Kernel模块名调用,也可以直接通过对象对用,如下代码:

Kernel.public_instance_methods.grep(/send/) # [:send, :public_send]

可以知道send方法是Kernel的public instance method方法,那么这里又是什么原理或者方法,使得其可以通过两种方式调用,即可以通过对象调用,也可以被Kernel调用。

另外请教前辈,这些知识点您是怎么积累的?

#16楼 @runup 由你的疑问可以看出sendKernel上的public_instance_methods,那么send只能通过对象调用。然而又确实可以这样调用: Kernel.send(:object_id),那只能说明Kernel也是对象,和 module_methods 那种可单例方式调用形式是不一样的。

Kernel.is_a?(Object)          # => true
Kernel.object_id                 # => 70359638759860 可能不同

确实不敢当前辈,也是新手,以你的钻研劲头,何愁大事不成。《Ruby 元编程第2版》还是可以看下的。

#17楼 @qinfanpeng 感激,在看第一版中文的,理解好之后再啃第二版的。

#17楼 @qinfanpeng 能不能帮忙看看我的新问题如何理解 Module 类里面的 module methods,感谢。

runup 回复

我想说说一点我的看法,我也是新手.

cc方法是在KernelModule模块中的实例方法,include进入FatherClass后

因为Mix-in的关系,会变成FatherClass的实例方法,SonClass继承之后,成为子类的实例方法

而类中是不能直接调用实例方法的

所以,直接在类中cc会报错的

class SonClass < FatherClass
  self.new.cc
end

是可以调用的,但是现在还有一个问题,就是在module中将cc声明为私有方法

所以,即使这样,调用(访问)还是失败的,去掉private,就可以使用了

有一点要纠正的是,

但是其实SonClass也是一个对象,是Class的实例化对象:

这句话没问题,但是,cc并不是Class中的实例方法,即使SonClass理解为Class类的实例对象,也不能调用此方法

而且,SonClass并未继承Class类,其继承的是Object类,所以如果这样搞

class << Object
  include KernelModule
end

就可以直接在类中cc了,当然private也是不行的

就像@fighterleslie 说的,从继承机制上可以看出来

最后,看了一眼帖子的时间,挖了一个坟帖,抱歉了

只是进行问题的交流,并无其他恶意

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册