Ruby eval, class_eval, instance_eval
Ruby eval简单用法
1 | eval(string) |
例如:
1 | str="hello" |
需注意,eval有自己的作用域环境,它相当于在一个语句块或Proc上下文,参考本文结尾的深入Ruby eval。
class_eval和instance_eval的用法
除了eval,Ruby还有class_eval
(还有等价的module_eval)和instance_eval
。用法为:
1 | # C或i是方法的receiver |
- class_eval:在指定的接收者C的上下文中执行class_eval中定义的相关代码,C是类名或模块名(类也是模块)
- instance_eval:在指定的接收者i的上下文中执行instance_eval中定义的相关代码,i是实例对象名,注意,自定义类自身也是Class类的实例对象
使用这两个方法的优势在于接收者C
或i
允许是变量形式,从而动态地定义代码。
例如:
1 | class A;end |
A.class_eval
表示在类A的上下文中执行指定的代码(即定义m1方法和m2方法),因为上下文是类A,所以相当于:
1 | class A |
再例如instance_eval:
1 | class A;end |
A.instance_eval
中,因为A是自定义的类名,自定义类都是Class类的实例对象,所以A.instance_eval
表示在类A这个类实例中执行相关代码。所以,上面的m1是A的类方法,而不是实例方法。
a_obj.instance_eval
则表示在a_obj实例中执行相关代码,所以m2是a_obj的单例方法。
因为class_eval
和instance_eval
中执行的代码分别绑定在各自的上下文中,所以在各自的上下文中可以访问一些属于该上下文的属性。
例如:
1 | class A |
深入Ruby eval
当eval只有一个参数时,默认在『全局』上下文解析变量、方法等。但实际上,eval有自己的作用域环境,它相当于在一个语句块或Proc上下文。因此,eval中可以访问或修改上层变量,但如果在eval中定义局部变量,eval退出后局部变量将消失。
1 | a=3 |
可以为eval其指定第二个参数,使得string参数代表的Ruby Code在该参数对应的上下文中执行。换言之,eval将在指定的上下文中解析执行Ruby Code。
第二个参数是一个特殊参数:Binding对象,一般通过Kernel#binding
方法返回。Binding对象是一个绑定了指定上下文(作用域)的特殊对象,相当于封装了一个上下文对应的路径查找表(lookup table),通过Binding对象,可以找到其所绑定上下文中的属性,包括变量、方法等。
例如:方法m定义了局部变量a和b,同时返回一个Proc对象。
1 | def m |
注意,返回的这个Proc能够访问a和b和c变量,但是a和b变量在定义Proc时就已经能访问,而c变量只有在调用proc时才能访问(即类似于编译期间和运行期间的区别)。
所以,该Proc对象的上下文中,除了继承自Object而来的属性外,还有a和b两个变量,且在其执行时,其上下文还包括c变量。
所以,m.binding封装了m所返回的Proc对象的上下文环境,即包含变量a和b但不包含变量c的环境。将其指定为eval的第二个参数,使得eval第一个参数对应的代码中可以访问a、b两个变量。
如果在上面eval第一个参数中使用变量c,则报错:
1 | p eval("c * a * b", m.binding) |
如果在函数内执行binding()返回Binding对象,则该Binding封装的是该函数的上下文:
1 | def m |
可以在eval中修改Binding对象中封装的变量:
1 | def m |
最后简单分析一下eval官方手册给的示例:
1 | def get_binding(str) |
上面的get_binding()
返回的是函数get_binding
的上下文,该上下文中包含了参数str对应的局部变量,所以在eval第一个参数字符串中可以使用该局部变量。