Ruby的self和方法定义

7
class MyClass
  def one
    def two
    end
  end
end

obj = MyClass.new
obj.one
puts obj.method(:two).owner  #==> MyClass

在此,我在方法一内定义了方法二。方法一由MyClass(obj)的实例调用。 因此,在定义方法二时,self为obj。当我检查方法二的所有者时,它是MyClass。

obj.instance_eval do
  def three
  end
end

puts obj.method(:three).owner  #==> #<Class:#<MyClass:0x007f85db109010>>

在这个代码片段中,我对obj执行instance_eval操作,因此在定义方法three时self再次是obj。但是,当我检查three的owner时,它是obj的singleton类。
为什么会这样?除了self之外,还有其他东西决定了方法定义的位置吗?

非常好的调查,给你一个加一(+1)... :) - Arup Rakshit
谢谢,每当我觉得我已经掌握了事情的时候,这些怪癖就会出现并咬我一口 :) 话虽如此,如果你在顶层定义方法,那么这些方法将成为Object的私有实例方法,而不是main的单例方法(我在某个地方读到过这是故意的,只是特殊情况)。 - orange
哈,def self.two; end 会像 instance_eval 一样运行。 - fl00r
3个回答

8
我会使用 Kernel#set_trace_func 方法来解释底层发生了什么。首先看下面的代码和输出:
trace = lambda do |event,file,line,id,binding,klass|
    p [event,File.basename(file),line,id,binding,klass]
end


set_trace_func trace

class MyClass
  def self.bar;end
  def one
    def two
    end
  end
end

obj = MyClass.new
obj.one
obj.instance_eval do
  def three
  end
end

输出:

-----------------
----------------
-----------------
-----------------
-----------------
----------------- # part A
["c-call", "test.rb", 9, :singleton_method_added, #<Binding:0x83ab2b0>, BasicObject]
["c-return", "test.rb", 9, :singleton_method_added, #<Binding:0x83aaeb4>, BasicObject]
["line", "test.rb", 10, nil, #<Binding:0x83aab80>, nil]
["c-call", "test.rb", 10, :method_added, #<Binding:0x83aa900>, Module]
["c-return", "test.rb", 10, :method_added, #<Binding:0x83aa07c>, Module]
----------------------------- # part B
["line", "test.rb", 16, nil, #<Binding:0x83a976c>, nil]
["c-call", "test.rb", 16, :new, #<Binding:0x83a9488>, Class]
["c-call", "test.rb", 16, :initialize, #<Binding:0x83a90a0>, BasicObject]
["c-return", "test.rb", 16, :initialize, #<Binding:0x83a8e20>, BasicObject]
["c-return", "test.rb", 16, :new, #<Binding:0x83a8b28>, Class]
---------------------------
---------------------------
--------------------------- # part C
["c-call", "test.rb", 11, :method_added, #<Binding:0x83a7de0>, Module]
["c-return", "test.rb", 11, :method_added, #<Binding:0x83a79f8>, Module]
--------------------------- # part D
["line", "test.rb", 18, nil, #<Binding:0x83a7034>, nil]
["c-call", "test.rb", 18, :instance_eval, #<Binding:0x83a6c10>, BasicObject]
["line", "test.rb", 19, nil, #<Binding:0x83a65f8>, nil]
["c-call", "test.rb", 19, :singleton_method_added, #<Binding:0x83a61d4>, BasicObject]
["c-return", "test.rb", 19, :singleton_method_added, #<Binding:0x83a5ef0>, BasicObject]
["c-return", "test.rb", 18, :instance_eval, #<Binding:0x83a5d4c>, BasicObject]

解释:

看一下部分A下面的5行。它简单地告诉我们,当Ruby在类中找到def关键字时,它会通过调用钩子方法Module#method_added将该方法添加为该类的实例方法。相同的解释适用于部分C下面的两行。

现在,在obj.instance_eval {..}中发生了什么?

如果您查看下面D部分的行,就可以清楚地了解这一点。从最后一行开始第二行。在instance_eval块内,def third会调用钩子方法BasicObject#singleton_method_added,将third作为ob对象的singleton_method添加进去。

这就是MRI的编写方式。


2
很不错的追踪方法Kernel#set_trace_func,我之前并不知道它...它确实清楚地说明了发生了什么,但我仍然不清楚为什么当self的值相同时方法的所有权会不同? - orange

2

def不是一个方法,所以它在处理self时不需要像方法一样表现。这很令人困惑,因为这两种方式显然是等价的:

class Foo
  def one
    "one"
  end

  define_method(:two) { "two" }
end

显然这两个并不是一样的(Bar的实例没有define_method

class Bar
  def one
    def two
      "two"
    end
    "one"
  end

  def three
    define_method(:four) { "four" }
    "three"
  end
end

你可以将这视为嵌套的def属于类的论点。这是有道理的,因为只有在打开类作用域时才可能出现这种嵌套的def,因此它会影响每个实例。
反过来说,在instance_eval中的def被添加到单例类中也是有道理的,因为你已经明确地只打开了实例。如果它与另一种情况相同,那么它就会破坏封装性。
所以...基本上这是特殊行为。但这并不是完全没有道理的。

2

这在一篇由ruby核心贡献者yugui撰写的好文章中有详细解释:Ruby中的三个隐式上下文。基本上,有一个默认的定义上下文,它与self不同。没有明确定义为单例方法的方法最终成为默认定义上下文的实例方法。而moduleclass定义体则会改变默认定义上下文,而def则不会。另一方面,instance_eval确实会改变它。


当我定义方法二时,ic的默认定义者是self类,而当我执行instance_eval时,ic的默认定义者是self的单例类。同样地,当我在toplevel中定义方法时,它们成为Object的实例方法,因为默认定义者是Object而不是main的单例类。 - orange

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接