在 Ruby 中,方法puts
是Kernel
模块的单例方法。
通常,当一个模块被另一个模块include
或extend
时,该模块(而不是其单例类)将添加到继承树中。这有效地使模块的实例方法可用于模块或其单例类(分别用于include
和extend
)...但是,混合模块的单例方法仍然不可访问,因为模块的单例类永远不会添加到继承树中。
那么为什么我可以使用puts
(和其他内核单例方法)呢?
Kernel.singleton_methods(false)
# => [:caller_locations, :local_variables, :require, :require_relative, :autoload, :sprintf, :format, :Integer, :Float, :String, :Array, :Hash, :test, :warn, :autoload?, :fork, :binding, :exit, :raise, :fail, :global_variables, :__method__, :__callee__, :__dir__, :URI, :eval, :iterator?, :block_given?, :catch, :throw, :loop, :gets, :sleep, :proc, :lambda, :trace_var, :untrace_var, :at_exit, :load, :Rational, :select, :Complex, :syscall, :open, :printf, :print, :putc, :puts, :readline, :readlines, :`, :p, :system, :spawn, :exec, :exit!, :abort, :set_trace_func, :rand, :srand, :trap, :caller]
请注意,puts
似乎不是Kernel
上的实例方法:
Kernel.instance_methods.grep(/puts/)
# []
虽然Object
确实包括Kernel
Object.included_modules
# [Kernel]
据我所知,Kernel
的单例类(#<Class:Kernel>
)不会出现在任何物体的祖先中。is_a?
似乎同意这一点:
Object.is_a? Class.singleton_class # false
Object.is_a? Kernel.singleton_class # false
Object.singleton_class.is_a? Class.singleton_class # true
Object.singleton_class.is_a? Kernel.singleton_class # false
然而,由于某种原因,它们显示为每个对象的私有方法。
Object.puts "asdf"
# NoMethodError (private method `puts' called for Object:Class)
如果#<Class:Kernel>
方法没有出现在祖先链中,方法查找如何找到这些方法?
相关:
- 对象的拼音方法查找路径 类、
- 模块、其特征类和方法查找
- 注意:这与我要求的不同,因为这是类继承,所以
#<Class:Class>
继承自#<Class:Module>
- 注意:这与我要求的不同,因为这是类继承,所以
- 为什么模块的单例方法在混合的下游特征类中不可见?
你找错地方了。
方法如Kernel#Array
、Kernel#Complex
、Kernel#Float
、Kernel#Hash
、Kernel#Integer
、Kernel#Rational
、Kernel#String
、Kernel#__callee__
、Kernel#__dir__
、Kernel#__method__
、Kernel#`
、Kernel#abort
、Kernel#at_exit
、Kernel#autoload
、Kernel#autoload?
、Kernel#binding
、Kernel#block_given?
、Kernel#callcc
、Kernel#caller
、Kernel#caller_locations
、Kernel#catch
、Kernel#eval
、Kernel#exec
、Kernel#exit
、Kernel#exit!
、Kernel#fail
、Kernel#fork
、Kernel#format
,Kernel#gets
,Kernel#global_variables
,Kernel#initialize_clone
,Kernel#initialize_copy
,Kernel#initialize_dup
,Kernel#iterator?
,Kernel#lambda
,Kernel#load
,Kernel#local_variables
,Kernel#loop
,Kernel#open
,Kernel#p
,Kernel#pp
,Kernel#print
,Kernel#printf
,Kernel#proc
,Kernel#putc
,Kernel#puts
,Kernel#raise
,Kernel#rand
,Kernel#readline
,Kernel#readlines
,Kernel#require
,Kernel#require_relative
,Kernel#select
,Kernel#set_trace_func
,Kernel#sleep
,Kernel#spawn
、Kernel#sprintf
、Kernel#srand
、Kernel#syscall
、Kernel#system
、Kernel#test
、Kernel#throw
、Kernel#trace_var
、Kernel#trap
、Kernel#untrace_var
,Kernel#warn
对他们的接收器没有任何有用的作用。它们不调用私有方法,不访问实例变量,实际上完全忽略self
是什么。
因此,如果您这样称呼它们,将具有误导性:
foo.puts 'Hello, World!'
因为读者会被误导认为puts
对foo
做了一些事情,而事实上,它完全忽略了它。(这尤其适用于打印方法家族,因为也存在IO#puts
和朋友,他们确实关心他们的接收器。
因此,为了防止您误导性地使用接收器调用这些方法,它们被设为private
,这意味着它们只能在没有显式接收器的情况下调用。(显然,他们仍然会被召唤self
,但至少在视觉上不会那么明显。
从技术上讲,这些根本不是真正的方法,它们的行为更像是过程,但 Ruby 没有程序,所以这是"伪造"它们的最佳方式。
它们也被定义为单例方法的原因是,您仍然可以在Kernel
不在继承层次结构中的上下文中调用它们,例如:
class Foo < BasicObject
def works
::Kernel.puts 'Hello, World!'
end
def doesnt
puts 'Hello, World!'
end
end
f = Foo.new
f.works
# Hello, World!
f.doesnt
# NoMethodError: undefined method `puts' for #<Foo:0x00007f97cf918ed0>
而之所以需要单独定义它们,是因为实例方法版本private
。如果不是,那么你无论如何都可以调用Kernel.puts
,因为Object
包括Kernel
,而Kernel
是Module
的实例,Object
的子类,因此Kernel
是自身的间接实例。但是,这些方法是private
的,因此您将获得
NoMethodError: private method `puts' called for Kernel:Module
相反。因此,需要单独复制它们。实际上有一个辅助方法可以做到这一点:Module#module_function
。(这也用于Math
,您可以在其中调用例如Math.sqrt(4)
或include Math; sqrt(4)
.在这种情况下,您可以选择是否include
Math
,而Kernel
总是Object
预先include
。
所以,总而言之:这些方法被复制为Kernel
private
实例方法以及public
单例方法(实际上只是Kernel
的单例类的实例方法)。它们被定义为private
实例方法的原因是,它们不能使用显式接收器调用,并且被迫看起来更像过程。它们被复制为Kernel
的单例方法的原因是,只要显式接收器Kernel
,就可以在继承层次结构中不可用Kernel
的上下文中调用显式接收器。
看看这个:
#ruby --disable-gems --disable-did_you_mean -e'puts Kernel.private_instance_methods(false).sort'
Array
Complex
Float
Hash
Integer
Rational
String
__callee__
__dir__
__method__
`
abort
at_exit
autoload
autoload?
binding
block_given?
caller
caller_locations
catch
eval
exec
exit
exit!
fail
fork
format
gets
global_variables
initialize_clone
initialize_copy
initialize_dup
iterator?
lambda
load
local_variables
loop
open
p
pp
print
printf
proc
putc
puts
raise
rand
readline
readlines
require
require_relative
respond_to_missing?
select
set_trace_func
sleep
spawn
sprintf
srand
syscall
system
test
throw
trace_var
trap
untrace_var
warn
"如果#没有出现在祖先链中,方法查找如何找到这些方法?">
1.class.included_modules # => [Comparable, Kernel]
引用 OP:
Kernel.instance_methods.grep(/puts/)
# []
正如您自己所发现的那样,私有实例方法不会出现,它们会显示Kernel.private_instance_methods
。
事实证明,答案是我问错了问题。为什么我可以使用像puts
这样的内核单例方法?答案是:你不能。
Kernel
的单例方法,与模块上所有其他单例方法一样,是不可继承的。诀窍在于它们本身不是单例方法......它们是模块函数。
在 ruby 中创建模块函数会创建该方法的两个副本:单例方法和私有实例方法。这就是为什么Kernel.singleton_method(:puts)
和Kernel.instance_method(:puts)
都有效。
因此,由于Object
include
Kernel
,它可以访问其实例方法,包括puts
。
我使用#instance_methods
犯了一个错误,它只显示公共实例方法。要查看私有的,我需要使用#private_instance_methods
,例如:
Kernel.private_instance_methods(false).grep(/puts/)
# [:puts]