Ruby自我和方法定义

时间:2013-12-01 11:43:29

标签: ruby self

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,所以当定义方法三时,self再次成为obj。但是当我检查三个人的主人时,它是obj的单身类

这是为什么?除了self之外还有什么能决定方法定义的位置吗?

3 个答案:

答案 0 :(得分:9)

我会用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_evaldef third旁边,third会将singleton_method添加为{对象ob的{​​1}},通过调用钩子方法BasicObject#singleton_method_added

这就是 MRI 的写法。

答案 1 :(得分: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只能是 ,所以它会影响每个实例。

另一方面,def中的instance_eval被添加到单例类中是绝对有意义的,因为您只显式打开了实例。它会破坏封装,使其与其他情况一样工作。

所以...基本上是它的特殊行为。但这并非完全没有意义。

答案 2 :(得分:2)

ruby​​-core贡献者yugui的一篇很好的文章解释了这一点:Three implicit contexts in Ruby。基本上,有一个默认的定义上下文,self相同。未明确定义为单例方法的方法最终作为默认定义上下文的实例方法。 moduleclass定义主体更改默认定义上下文,而def则不会。 instance_eval OTOH 更改它。