Ruby 元編程的三種 eval
今天讓我們來學習 Ruby 元編程的三種 eval:eval、instance_eval、class_eval。
1. eval
eval
可以將字符串作為代碼進行執(zhí)行,并返回代碼的返回值。
它的使用方法是eval(code_string)
。
code_string
可以是完整的Ruby代碼、或表達式。
實例:
p eval("1 + 1")
# ---- 輸出結果 ----
2
解釋:在這里"1 + 1"是一個表達式的字符串,我們使用eval來將這個字符串當做表達式進行執(zhí)行,得到返回值2.
當然,如果字符串里面代碼運行報錯也會拋出異常。
實例:
eval(" puts a ")
# ---- 輸出結果 ----
Traceback (most recent call last):
2: from ruby.rb:1:in `<main>'
1: from ruby.rb:1:in `eval'
ruby.rb:1:in `eval': undefined local variable or method `a' for main:Object (NameError)
讓我們使用eval
來定義一個類。
eval <<-DEFINED_CLASS
class Person
def name
'Andrew'
end
end
DEFINED_CLASS
person = Person.new
p person.name
# ---- 輸出結果 ----
"Andrew"
解釋:這里我們使用heredoc來定義了一個多行字符串,里面的內容是定義一個類Person
,并在類里面定義了一個實例方法name
。從最后輸出結果來看,我們成功的創(chuàng)建了這個類以及對應的實例方法。
Tips:
eval
功能非常強大,靈活度也很高,但是它很危險,有點類似于SQL注入。
比如我們定義了一個方法,可以打印出我們傳入的東西。
實例:
str = "Hi"
eval %Q{
def method
puts #{str}
end
}
method
# ---- 輸出結果 ----
Hi
但是如果這個str
可以被外界篡改,就會產生非常大的問題。
實例:
str = "'Hi'; system('touch i_am_hack && echo \"I am hack\" > i_am_hack'); system('ls');"
eval %Q{
def method
puts #{str}
end
}
method
# ---- 輸出結果 ----
Hi
你當前所在的目錄結構!!!!
解釋:當輸出Hi的同時,我們創(chuàng)建了一個i_am_hack的文件,并且獲取了執(zhí)行腳本所在文件夾目錄結構。
所以,請輕易不要使用eval
。
Tips:有時,eval = evil。
2. instance_eval
從字面意思上來看,我們可以理解為是專門為實例對象做的eval
,這個想法是對的。
實例:
class Person
end
person1 = Person.new
person2 = Person.new
person1.instance_eval do
def name
'Andrew'
end
end
p person1.name
p person2.name
# ---- 輸出結果 ----
"Andrew"
Traceback (most recent call last):
ruby.rb:12:in `<main>': undefined method `name' for #<Person:0x00007fa0d0047f68> (NoMethodError)
解釋:從上面的例子我們可以看到,我們對實例person1
增加了一個name
方法,增加的這個方法只作用到了person1上面,沒有作用到person2上面,由此我們可以推斷出,instance_eval
的修改只是針對被操作的對象。
之前我們學習到,Ruby的類也是對象,那么我們可以對類進行instance_eval
操作嗎,答案是可以的。
實例:
class Person
end
Person.instance_eval do
def name
'Andrew'
end
end
p Person.name
# ---- 輸出結果 ----
"Andrew"
解釋:因為所有的類都是Class
類的實例,所有,我們在對類進行instance_eval
時,相當于拓展了它的類方法。這種也是instance_eval
最常用的功能。
3. class_eval
根據方法名來看,我們猜測了這個是對類做的eval
,沒錯確實是這樣的,它常常用于給類添加實例方法。
實例:
class Person
end
Person.class_eval do
def name
'Andrew'
end
end
person1 = Person.new
person2 = Person.new
p person1.name
p person2.name
# ---- 輸出結果 ----
"Andrew"
"Andrew"
解釋:上面示例中,我們?yōu)?code>Person這個類添加了name
的實例方法,由輸出可知,我們成功的在Person
的每個實例下都添加了name
方法。
我們也可以從定義的方法中獲取到對應類的實例變量。
class Person
def initialize name
@name = name
end
end
Person.class_eval do
def name
@name
end
end
person1 = Person.new 'Tom'
person2 = Person.new 'Bob'
p person1.name
p person2.name
# ---- 輸出結果 ----
"Tom"
"Bob"
Tips:class_eval 和 instance_eval 后面不僅可以使用 block,還可以直接添加字符串,類似于eval,但是同樣不建議使用,同樣會導致不安全的問題。
實例:
class Person
end
Person.class_eval <<-DOC
def name
'Andrew'
end
DOC
p Person.new.name
# ---- 輸出結果 ----
"Andrew"
4. 小結
今天我們學習了 Ruby 中的三個 eval,其中eval
的擴展性最強,但最危險,instance_eval
用于擴展類方法,class_eval
用于擴展類的實例方法。