Ruby 对 class_eval 和 instance_eval 的理解

匿名 · 2012年04月02日 · 最后由 hooopo 回复于 2012年07月01日 · 5496 次阅读

之前对 class_eval 和 instance_eval 的理解不够。在查看了相关资料后,才明白,原来在代码中,随时有这三个隐形引用:self、default_definee、cref。 其中:

  • self 代表 receiver
  • default_definee,则表示代码中声明的方法属于哪个类
  • cref 则表示定义的常量属于哪个类

对于 receiver,大家应该都清楚。 而对于 default_definee,在 class_eval 中,则还是 receiver;在 instance_eval 中,default_definee 则变成了 receiver 所指向的 eigenclass。

因此下面的代码也就不难理解了:

class A; end
A.class_eval do
  def a
    p :a
  end

  def self.sa
    def b
      p :b
    end
    p :sa
  end
end

A.instance_eval do
  def c
    p :c
  end

  def self.sc
    def d
      p :d
    end
    p :sc
  end
end


执行下面的语句,将输出:

A.instance_methods false # => [:a]
A.sa => :sa
A.instance_methods false # => [:a, :b] 
A.c => :c
A.sc => :sc
A.instance_methods false # => [:a, :b] 
A.d => :d


Pragmatic 的 metaprogramming 书和视频有对 eigenclass 做详细的解释。:)

谢谢楼主热心讲解。我也一直觉得应该有个类似的隐形引用的。

不过我要指出楼主代码的几个问题:

  • 楼主的示例,我觉得只会让原来不明白的人更不明白,呵呵。
A.instance_eval do
...
end

上面这个例子也不妥,会让人误解,因为 instance_eval 应该总是在receiver 作为一个对象而存在的情况下, 被使用 类似楼主的这种伪码几乎在任何情况下都不会真正发生的。换个说法,如果你想定义一个类的单例方法,根本不该这么用。而且,会增加怎么def cdef self sc, 定义成一样的方法的烦恼。

  • 有关 instance_eval 和 class_eval, 我觉得如果研究太细,反而会更加糊涂。我觉得以下解释方式最明了.
    • instance_eval, 代码块内的代码,会在 instance_eval 这个方法的 self 之上被调用,不应该总是把 eigenclass 这个概念牵扯进来。
    • class_eval, 你只需要简单的理解为:给 receiver 所在的类定义内添加了几行代码即可。
匿名 #3 2012年04月02日

#2 楼 @zw963 谢谢耐心回帖。 “如果你想定义一个类的单例方法,根本不该这么用。而且,会增加怎么 def c 和 def self sc, 定义成一样的方法的烦恼.” —— 赞同!的确,在真正实践的时候,代码应是直接了当,简单明了,呵呵。

“有关 instance_eval 和 class_eval, 我觉得如果研究太细,反而会更加糊涂。” —— 这点我觉得看个人兴趣吧,有人可能想研究深一点,也有人可能想先知道怎么用就可以。呵呵。

匿名 #4 2012年04月02日

#1 楼 @fredwu 谢谢啊,再去了解一下。

#3 楼 @imsoz

嗯,事实上,我觉得在 Ruby1.9 下面,唯一的可能用到类.instance_exec的情况是:

C.instance_exec(:m) {define_method(:m) {...}}  # => 动态创建一个实例方法. 即常见的attr_accesor魔法.

以前 1.8 可能还会用来动态创建类方法。不过 1.9 专门提供了 define_singleton_method 来做这个事情。

匿名 #6 2012年04月02日

#5 楼 @zw963 原来还有 define_singleton_method 这个方法。学习了:)

匿名 #7 2012年04月02日

#5 楼 @zw963 另外再请教一下,像 define_method 这样的 private method,哪里可以有详细的文档?

#7 楼 @imsoz
谈不上请教,客气啦. 我一般就是看 Ruby 自带的 ri 文档或者翻镐头书,另外,我会记笔记把类似的方法归纳一下。加深理解. 官方最新的文档,可以在这里查询:http://ruby-doc.org/core-1.9.2/

不过这里貌似没有 define_method 的说明。呵呵。

匿名 #9 2012年04月02日

#8 楼 @zw963 哈,我刚刚也去查了,最新的文档是 1.9.3。上面也没有 define_method 这个方法。我是写 java 的,就觉得 private 方法是内部的,随时有可能更改。而在 ruby 中,private 方法也可以随意使用,但 ruby doc 却没有相关文档。不知道会不会使用 private 会存在一定的升级风险?

#7 楼 @imsoz 可以用 https://github.com/voloko/sdoc/ 来自己生成文档,里面有选项 --all,可以把 private 方法也生成到 doc 里。

#9 楼 @imsoz

Ruby ri 文档里有 define_method 啊。一般元编程时才会用的。 ruby 中,不是 private 随时可以使用,是混入到 Kernel 模块的私有方法可以随意使用. 像 define_method 只能在类定义内使用。或者其他 self 必须是一个类的环境,例如 instance_eval

匿名 #12 2012年04月03日

#11 楼 @zw963 “ruby 中,不是 private 随时可以使用,是混入到 Kernel 模块的私有方法可以随意使用.” —— 明白了你的意思。 在其他的场景,比如可以使用 send,或者 instance_eval 来调用私有方法,也是可以的。这是不是意味着,有些方法,虽然是 private 的,但其实一旦修改,可能也会造成使用者出现问题?

#12 楼 @imsoz

类似于混入 Ruby Kernel 模块或者 Ruby Module 模块的私有方法,主要目的我个人的感觉是以下原因:

  1. 方便使用,例如,混入 Kernel 的方法就像全局函数一样。
  2. 限制只能在某个场合使用,例如:例如 define_method, 这个方法只能在 Module 或 Class 类实例作为 receiver 的情境下,像全局函数一样被使用。例如类定义内。

也许我没明白你的意思。不过我觉得是不是你对 Ruby 的 private 这个关键字还没理解呀。private 和其他语言的使用方式不完全一样的。

匿名 #14 2012年04月03日

#13 楼 @zw963 有可能我没有理解透。 我的理解是,ruby 中的 private 方法,很可能也需要保持兼容(除非人工声明对 private 方法不做兼容) 或者说 ruby 中调用 private 方法的途径还是蛮多的(只要不显示的声明 receiver,就可以调用当前 self 的 private method)。 比起 java 使用反射来使权限变为 public,ruby 的路子要多得多。 当然不是说这种方式不好,而是我觉得很可能对 ruby developer 的要求更高,在使用强大的工具之后,如何保证之后的可维护性——很可能就是能力越大,责任越大吧。

#14 楼 @imsoz

嗨~

我没用过 java, 之前N年前用过C++, 不过这么多年没用,所以作为我来说,一个好处就是:不会按照其他语言的思路来想 Ruby. 我觉得这是你必须先过的一关吧。不要总和 Java 这样的静态语言比较,Ruby 就是 Ruby. 他和之前的大多数语言都不一样。

有关 private, 其实我不明白你为什么老关注什么是否更改啊,兼容之类的。

如果是在你自己的类中使用 private, 别人根本不会知道,也不会被调用。 但是如果在一个 DSL 中 (例如你编写了一个框架), 那么 private 就代表这是一个全局函数。他作为 DSL 的一部分,会给定义特殊的东西,带来便利。

其实我也没想透。糊里糊涂的,但是我能感觉到你现在有个问题,就是你可能并没有意识到,在 Ruby 中,一切都是对象这个价值观,其实在 Ruby 中,任何东西, 都是在 self 之上被调用的。包括顶级空间。

匿名 #16 2012年04月03日

在 Ruby 中,一切都是对象这个价值观,其实在 Ruby 中,任何东西,都是在 self 之上被调用的。包括顶级空间。

这个应该是没有问题的,顶级其实就是一个 main 对象,包括直接输入 p :a,其实 p 就是一个 Kernel 的 private 方法,只不过没有显示指定 receiver,因此可以调用,interpreter 会沿着 main 的 class ref 找到 Object,再找到 Kernel。

我想表达的意思是,似乎 ruby 中对面向对象的封装性,在一个 javaer 看来,也不是很“封装”。比如在 instance_eval(class_eval)或者 Kernel#send 时。 我想这会带来便利,但在带来便利的同时,也需要考虑到之后的可维护性。这是我的理解。

我想 javaer 刚开始写 ruby,或多或少都会碰到传说中的语言绑架思想的状况吧。

匿名 #17 2012年04月03日

如果是在你自己的类中使用 private, 别人根本不会知道,也不会被调用。 但是如果在一个 DSL 中 (例如你编写了一个框架), 那么 private 就代表这是一个全局函数。他作为 DSL 的一部分,会给定义特殊的东西,带来便利。

这个貌似有点想通了,比如打开 Kernel,并添加 private 方法,那么其实就和 puts 类似了:)。 从这个方面来看,这个时候,private 其实就不是传统意义上(或者说是 java 中)的 private 了。这个时候 private 只是保证了不能显示指定 receiver 调用。谢谢:)

在 ruby 中方法调用形式分为

  1. CALL 带 self 的方法调用,如 self.func('a')
  2. FCALL 不带 self 的方法调用,如 func('a')
  3. VCALL 没有参数的 FCALL, 如 func

对于 FCALL 和 VCALL,ruby 并不校验该方法是否 private,这也正是实现了那种调用全局函数的效果,比如:

def global_func
  puts 'global_func'
end

class A
  def method
    global_func  # 如果使用self.global_func, 则提示global_func为private
  end
end

a = A.new
a.method

匿名 #19 2012年04月03日

#18 楼 @xzgyb 请教一下,像 CALL、FCALL、VCALL,是属于 ruby 内部实现机制么?在哪里可以找到相关资料?谢谢啦:)

#19 楼 @imsoz 你可以参考 Ruby 的 C 源码。

但是个人觉得:用好 Ruby 的关键不在于看 Ruby 的 C 实现。虽然可以帮助你从另一角度来理解 Ruby,但是真正的用好 Ruby,写好 Ruby 代码,真心的不用看 Ruby 的 C 实现。举个例子:DHH 和 Yehuda Katz 两个 Ruby 牛就没有 C 语言的背景,这个是可以确定的,但是有谁敢说对 Rails 的理解有 DHH 深呢?

注意你是学 Ruby 而不是学 C。如果你想学动态语言的设计,或是 GC 的实现等,Ruby 和 Lua 的代码都是很好的素材。如果你想写 Ruby 的扩展,那代码也是需要看的。如果你是学Ruby 语言,那 ruby 是你学习的重点。我不反对看 Ruby 的 C 源码,但是最好还是在掌握好 Ruby 的基础之后。

换句话说,Ruby 就是 Ruby,CRuby 只是一种实现而已,其他语言的实现 JRuby 于 IronRuby 于 CRuby 的实现就差异巨大。所以 Ruby 语言本身才是学习的重点。

咱们社区里的 Rails 牛们也不见得都有 C 背景,C 背景和你能否称为 Ruby 牛或是 Rails 牛没有一点关系。罗嗦这么多~ 希望能帮到你少走弯路。

匿名 #21 2012年04月03日

#20 楼 @skandhas 呵呵,谢谢!我是 javaer,也没有 C 背景:) 我知道看 C 对我的挑战很大,但我也还没有看源码的打算。 只是我觉得,要理解一样东西,就必须往下一层去理解这东西,这样就知其所以然。 所以我刚刚想咨询 CALL,FCALL,VCALL 的资料,以了解这个设计初衷。

你的建议很有价值,对我很有用,谢谢哈:)

其实就涉及两个东西,理解清楚就行了,current class 和 current object.

#21 楼 @imsoz 客气了。如果想了解 Ruby 的设计,而且你又熟悉 Java,完全可以看 JRuby 的代码来开阔视野,犯不着看你不熟悉的 CRuby。

就学好 Ruby(不包括 Rails) 来说,我觉得双飞燕和搞头书,再加上 api doc 基本就可以了。“要理解一样东西,就必须往下一层去理解这东西”这个道理我赞同,只是要往下探索到哪一层呢?个人认为目前可以到 Ruby 语言层即可,因为再往下就不属于 Ruby 范畴,而是属于语言设计范畴了。后期可以根据需要可以深入到 C 层。

举例来说的话,学好 C/C++ 不一定要去读懂 gcc 的所有代码。学好 java 不一定非要去读 HotSpot 的代码。作为提升功力,在掌握好语言本身之后,再进入下一个阶段比较好~。不然,很有可能事倍功半阿。

@imsoz, 不客气。

CALL、FCALL、VCALL 这几个,是属于 ruby 内部实现机制,

ruby1.9.3 p0 compile.c:4050

  case NODE_CALL:
  case NODE_FCALL:
  case NODE_VCALL:{     /* VCALL: variable or call */
/*
  call:  obj.method(...)
  fcall: func(...)
  vcall: func
*/

这个纯属个人爱好,因为看到过,所以这里说上几句,不好意思,希望不会误导到你。

因为对于 private 为什么会是这样的设计,我也疑惑过,所以这里在多说一下, 权当做参考。

在全局作用域(也就是一般说的 toplevel)中定义的方法,在某个类中可以直接调用, 类似其他的语言中的全局函数的效果,原因就是初始脚本运行时,ruby vm 默认打开了 Object 类,并且设置方法的默认可见性为 private, 则在 toplevel 中定义的方法都是 Object 的方法且是 private 的, 由于 ruby 中的一切都是从 Object 继承的, 故 ruby 对于 private 方法的这一设计,使得在任何类的方法中, 都可以用这种类似全局函数调用的形式来进行调用。

另外附带说一句, kernel 中的 load 函数 load( file_name, wrap=false )

如果 wrap 设为 true, 就是使得 ruby vm 默认不是用 Object,而是创建一个匿名的 module, 目的是不让它污染 toplevel 的名字空间。

另外同意@skandhas 的说法,人生苦短,ruby 的宗旨就是让程序员快乐的编程。

匿名 #26 2012年04月04日

作为提升功力,在掌握好语言本身之后,再进入下一个阶段比较好~。不然,很有可能事倍功半阿。

这个表示赞同。 就像学框架,还不会使用框架,就去看框架的源码,是最容易事半功倍的。

没注意又翻到了这个帖子,补充一下:class_eval 和 instance_eval 的一个重要区别是前者的 current class 是 receiver,而后者的 current class 是 receiver 的 eigen class

#26 楼 @imsoz 事半功倍??

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