Monkey Patching: Python V.S. Ruby
之前在寫Rails的時候還不是很熟悉Ruby,所以很多Ruby的特性沒有去深究。最近剛考完期末考比較有時間了,所以好好來看了一下Ruby。目前覺得學Ruby的好處就是會讓我想去比較Python,因為我個人還是Python的愛好著,所以看Ruby可以做到的功能就想要看Python可不可以做到。拿Ruby最有名的名能之一來說好了: Monkey Patching,從Wikipedia:
這時候 Array 就會多了一個名為 'test' 的 class method,事實上 test 其實是 Array 這個 class 的 singleton method,因為 Ruby 裡 class 是 first class object,所有的 class 都是 Class 的 instance。 所以這個動作就是在 Array 這個 'instance' 上加入一個 singleton method。同樣的動作也可以寫成
或
來測試一下Array是不是真的有一個test method:
Python 一樣可以做到 Monkey patching
可以看到上面我們把 hashlib 裡的 md5 function 替換掉了。不過Python有一個限制就是不能Monkey patch 'built-in' type:
Ruby 也有類似的功能可以禁止Monkey patching
不過 Ruby 的 built-in 大多沒有被freeze。我想這是設計哲學的問題,個人來說我比較喜歡Python的方式。在 Python 裡如果你想要延伸 built-in class 的功能的話,直接繼承就好了。這樣的好處是你在使用這個 class 時,class 的名稱就會提醒你這是你改過 class;如果直接用像 Ruby 常用的 monkey-patching 的話,你或其他用你的 class 的 programmer 會不知道或忘記你已經修改了原本那個class的預設行為,需造成不必要的錯誤,也很難 debug。所以我還是比較喜歡 Python 哲學:
http://madebydna.com/all/code/2011/06/24/eigenclasses-demystified.html
A monkey patch is a way to extend or modify the run-time code of dynamic languages without altering the original source code.
Ruby 可以很容易的動在舊有的Class/Module中加入新的Class/Method,舉例來說我們可以新增一個 method 到 Array 中:
def Array.test puts "This is a singleton method `test' of the Array class" end
這時候 Array 就會多了一個名為 'test' 的 class method,事實上 test 其實是 Array 這個 class 的 singleton method,因為 Ruby 裡 class 是 first class object,所有的 class 都是 Class 的 instance。 所以這個動作就是在 Array 這個 'instance' 上加入一個 singleton method。同樣的動作也可以寫成
class << Array def test puts "This is a singleton method `test' of the Array class" end end
或
class Array def self.test puts "This is a singleton method `test' of the Array class" end end
來測試一下Array是不是真的有一個test method:
irb(main):006:0> Array.respond_to? :test => true irb(main):007:0> Array.test This is a singleton method `test' of the Array class => nil
Python 一樣可以做到 Monkey patching
>>> import hashlib >>> def fake_md5(data): ... print 'Replacing original md5' ... >>> hashlib.md5 = fake_md5 >>> hashlib.md5('bla') Replacing original md5
可以看到上面我們把 hashlib 裡的 md5 function 替換掉了。不過Python有一個限制就是不能Monkey patch 'built-in' type:
>>> def test(): ... print 'aha!' ... >>> list.test = test Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: can't set attributes of built-in/extension type 'list'
Ruby 也有類似的功能可以禁止Monkey patching
irb(main):001:0> require 'matrix' => true irb(main):002:0> Matrix.frozen? => false irb(main):003:0> Matrix.freeze => Matrix irb(main):004:0> Matrix.frozen? => true irb(main):005:0> def Matrix.test irb(main):006:1> puts 'Monkey-patched method test' irb(main):007:1> end RuntimeError: can't modify frozen Class from (irb):5 from /usr/bin/irb:12:in `<main>'
不過 Ruby 的 built-in 大多沒有被freeze。我想這是設計哲學的問題,個人來說我比較喜歡Python的方式。在 Python 裡如果你想要延伸 built-in class 的功能的話,直接繼承就好了。這樣的好處是你在使用這個 class 時,class 的名稱就會提醒你這是你改過 class;如果直接用像 Ruby 常用的 monkey-patching 的話,你或其他用你的 class 的 programmer 會不知道或忘記你已經修改了原本那個class的預設行為,需造成不必要的錯誤,也很難 debug。所以我還是比較喜歡 Python 哲學:
Beautiful is better than ugly.Reference:
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Readability counts.
http://madebydna.com/all/code/2011/06/24/eigenclasses-demystified.html
留言