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