精华帖 (0) :: 良好帖 (7) :: 新手帖 (3) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2009-08-02
最后修改:2009-08-08
Ruby的evaluation是一个很重要的功能,它可以eval一个字符串或者一个block。在一些适宜的情况下使用它会得到一些“意外”的效果。
常用的eval
Ruby常用的evaluation有:class_eval (module_eval),instance_eval ,eval 。
这三种evaluation方法可以在不同的情况下使用:
1. class_eval
class_eval和module_eval是相同的,class_eval是module_eval的一个alias。
class_eval可以在一个mod的上下文中eval一个字符串或者一个block,常用于给一个类添加方法(实例方法):
class Thing end a = %q{def hello() "Hello there!" end} Thing.module_eval(a) puts Thing.new.hello() #=> "Hello there!" 2. instance_eval
instance_eval可以在一个实例的上下文中eval一个字符串或者一个block:
class Klass def initialize @secret = 99 end end k = Klass.new k.instance_eval { @secret } #=> 99
3. eval
eval是在当前上下文中eval一个字符串,如果指定一个binding,则在binding的上下文中eval。
def getBinding(str) return binding end str = "hello" eval "str + ' Fred'" #=> "hello Fred" eval "str + ' Fred'", getBinding("bye") #=> "bye Fred" class_eval和instance_eval的不同
首先注意到class_eval和instance_eval的不同主要在于执行上下文(context)的不同。class_eval在一个mod的上下文中执行,而instance_eval在一个实例的上下文中执行。
其次,他们的常用场景不同。class_eval的应用场景一般是“打开一个类”来做一些事情,比如增加方法,或者是include一个module。
String.class_eval do include ExtraMethods def another_method .... end end 而instance_eval主要关注于一个实例。
class Paginator def initialize total_entires @total_entries = total_entires @page_index = 0 end def next @page_index += 1 end end paginator = Paginator.new(100) paginator.next paginator.instance_eval "@page_index" #=> 1 paginator.instance_eval { @page_index } #=> 1 当然,我们也同样可以通过class_eval给Paginator类增加一个方法来获取page_index。
在我们了解instance_eval和class_eval之前,很可能会误用它们,比如通过下面的方法给Paginator增加一个实例方法:
Paginator.instance_eval do def baz "baz" end end 结果:
Paginator.new.baz #=> undefined method ‘baz’ for #<Foo:0x7dce8>
但我们偶然发现:
Paginator.baz #=> "baz" 其实,如果我们了解ruby的对象模型,对这个结果并不会意外。我刚才已经说过:instance_eval关注于一个实例,它是在一个实例的上下文中执行的。Paginator类本身就是一个Class类的实例,所以Paginator.instance_eval做的就是给这个Class实例--Paginator--增加一个方法,也就是Paginator的类方法。
同样,我们可以通过instance_eval给任意类的实例增加方法,比如:
"good".instance_eval do def opposite "bad" end end "good".opposite #=> "bad" 这个opposite方法就是"good"实例的一个singleton method。
instance_eval和DSL
DSL的一个主要特点是可以在不同的上下文中执行,下面就是一个例子(窃取自Jay Field举的例子):
class SqlGenerator class << self def evaluate(&script) self.new.instance_eval(&script) end end def multiply(arg) "select #{arg}" end def two(arg=nil) "2#{arg}" end def times(arg) " * #{arg}" end end SqlGenerator.evaluate { multiply two times two } #=> "select 2 * 2" class Calculator class << self def evaluate(&script) self.new.instance_eval(&script) end end def multiply(arg) eval arg end def two(arg=nil) "2#{arg}" end def times(arg) " * #{arg}" end end Calculator.evaluate { multiply two times two } #=> 4 简单得介绍了一下ruby的evaluation,要了解更多,可以看下面这两篇文章:
http://www.infoq.com/articles/eval-options-in-ruby
http://blog.jayfields.com/2007/03/ruby-instanceeval-and-classeval-method.html 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2009-08-03
ruby for rails 这本书里介绍得比较详细
|
|
返回顶楼 | |
发表时间:2009-08-03
原来
SqlGenerator.evaluate { multiply two times two } 是 SqlGenerator.evaluate { multiply(two(times(two))) } 的意思啊 ,看起来好神秘。。 |
|
返回顶楼 | |
发表时间:2009-08-03
Hooopo 写道 原来
SqlGenerator.evaluate { multiply two times two } 是 SqlGenerator.evaluate { multiply(two(times(two))) } 的意思啊 ,看起来好神秘。。 可以把空格理解为右结合的运算符 ~ ruby 1.9 还有一种 eval 方法 is = RubyVM::InstructionSequence.compile('1+1') is.eval 这个东西还能用来看 YARV 字节码: pp is.to_a puts is.disasm |
|
返回顶楼 | |
发表时间:2009-08-03
我觉得class_eval和instance_eval这两个名字起的有些晦涩,还有module_eval和class_eval是一个意思这也让人难以理解。。。
我觉得class_eval如果这样比较合理些: class Thing end class Module def my_class_eval(*args) if block_given? yield else eval *args end end end str = %q{ def hello puts "hello" end } Thing.my_class_eval(str) Thing.my_class_eval do def say_hi puts "Hi~~" end end Thing.hello # => hello Thing.send(:say_hi) # => Hi~~ #不知道为什么用block后成了private方法,只好用send了。。 |
|
返回顶楼 | |
发表时间:2009-08-04
在 Thing 里头加一个 public 就行了 ……
Thing; public;end public、private 会传播是个陷阱 …… def 之前最好标一标 ps:class 和 module 会返回块内最后一条语句的值。 class T; @@a = 3;end a = class T;@@a;end #=> 3 |
|
返回顶楼 | |
发表时间:2010-05-18
最后修改:2010-05-18
>> Thing.my_class_eval do ?> def say_hi >> puts 'hi---' >> end >> end => nil >> Thing.say_hi hi--- 为哈子我的还是public的呢? >> "good".instance_eval do ?> def opposite >> "bad" >> end >> end => nil >> ?> "good".opposite #=> "bad" NoMethodError: undefined method `opposite' for "good":String from (irb):51 >> a='good' => "good" >> a.instance_eval do ?> def ss >> 'ss' >> end >> end => nil >> a.ss => "ss" 1.87下跑的。结果不太一样。 |
|
返回顶楼 | |
发表时间:2010-05-18
eval的速度也是慢得可以
|
|
返回顶楼 | |
浏览 3887 次