C++,python,热爱算法和机器学习
全部博文(1214)
分类: Python/Ruby
2014-12-28 00:35:33
Ruby 元编程中,经常可以看到各种 eval 和 heredoc 的结合。其中很多用 heredoc 的位置都会出现 __FILE__ 和 __LINE__ 这两个变量。本文介绍一下它们的作用,以及为什么要这样做。
先看一个用 class_eval 动态生成实例方法的例子:
1
2
3
4
5
6
7
8
9
10
11
|
class A
def self.my_attr_reader(*args)
args.each do |method|
class_eval <<-EOF, __FILE__, __LINE__
def #{method}
raise "#{method}"
end
EOF
end
end
end
|
这是模仿 Ruby 的 attr_reader 的代码,当然实现简化过了,关键是,它会抛出异常。我们待会再解释为什么这样做。现在先大概解释下代码。
class_eval 是 Ruby 中 eval 的一种,它的上下文是调用它的对象,也就是类 A 。它通常用来为类生成实例方法,用法就像上面代码中的那样。你可以传把一段 Ruby 代码写在一个字符串中传过去,class_eval 就会动态解释 Ruby 代码。这和 Javascript 中的 eval 十分类似。
heredoc 就是被 EOF 包起来的那部分。它实际上是一个多行的字符串,EOF 只是一个标识,代表 heredoc 的开始和结束,你可以随意换成其他玩意。用 heredoc 的好处之一是可以不用操心单引号和双引号的问题,还有就是把代码写在多行看得比较清楚。喜欢深挖的可以看看 。
第4行 EOF 后面的 __FILE__ 和 __LINE__ 是两个特殊变量,它们保存了当前文件的名字和当前代码的行数。其实 heredoc 后面不加这两个变量也可以。比如这样也是合法的:
1
2
|
class_eval <<-EOF
EOF
|
那加了它们有什么好处呢?
主要还是在测试方面。我们可以用如下代码测试一下:
1
2
3
4
5
6
|
A.class_eval do
my_attr_reader :name
end
a = A.new
a.name # 会抛出异常,异常信息为 name
|
把这段代码和上面第一段放在一起,保存成 Ruby 文件(我保存成 test.rb),再用解释器执行,会看到如下的错误:
1
2
3
|
# test.rb:5:in `raise': exception object expected (TypeError)
# from test.rb:5:in `name'
# from test.rb:18
|
可以看到,异常信息会提示你错误出在 test.rb 第5行的 name 方法内。即使 name 方法是用元编程手段动态产生的。
现在我们把 EOF 后面的 __FILE__ 和 __LINE__ 去掉。再运行下代码,会看到如下的结果:
1
2
3
|
# (eval):2:in `raise': exception object expected (TypeError)
# from (eval):2:in `name'
# from test.rb:18
|
可以看到,异常信息最后是从 eval 中抛出来的,最多只能看到18行调用 name 的那一行出错了,但看不到 name 方法在哪定义的。这样对 debug 几乎没有帮助,尤其是当程序复杂的时候。比如 Rails 就使用了大量元编程技巧。
所以,__FILE__ 和 __LINE__ 在这里就是为了方便 debug 的。它们并不会对元编程动态创建的方法有任何影响。