剛剛在Godfat的Blog看到這篇文章,比較了Ruby和Python在對於list進行+=運算子操作的差異:
[~/test]# irb
irb(main):001:0> a = b = []
=> []
irb(main):002:0> a += [1]
=> [1]
irb(main):003:0> a
=> [1]
irb(main):004:0> b
=> []
irb(main):005:0>
Python 2.5.1
>>> a = b = []
>>> a += [1]
>>> a
[1]
>>> b
[1]
這個結果同樣的出乎我的意料之外,所以花了些時間研究一下。以下分別討論a=b=[]; a+=[1]在Ruby和Python的運作狀況。
先看看a=b=[]。毫無疑問,不管在Ruby或Python,這個敘述都是「造一個空list,並讓a和b指向同一個list」。如果你被上面的例子迷惑了,試試:irb(main):001:0> a = b = []
=> []
irb(main):002:0> a << 1
==> [1]
irb(main):003:0> a
=> [1]
irb(main):004:0> b
=> [1]
irb(main):005:0>
再來,先討論Python
Python的a+=[1]等價於a.iadd([1])
對呼叫a+=[1]的block,變數a所綁定的物件不會改變
既然a和b所綁定的物件相同,那a和b的內容理所當然也會一樣
再來看看Ruby的a+=[1]
雖然Ruby可以覆載運算子,但+=運算子是不能被覆載的
簡單的試試
Ruby的method可以名為+,可以以=結尾,就是不能名為+=。同理,-=, *=, /=, &&=, ||=之類的也不行。參考Programming Ruby
In common with many other languages, Ruby has a syntactic shortcut: a=a+2 may be written as a+=2.可以得到結論:a+=[1]只是一個Syntax sugar,可以想像他parse時就變成了a=a+[1],而不是由a去invoke一個名為+=的member function。
差異就在這邊,對呼叫a+=[1]的block來說,a+[1]造出了一個新物件,再綁定到變數a上,所以a和b指向不一樣的東西。
這裡或許值得注意的是,要是Ruby中+=是一個member method,那a+=[1]就很難不影響到b。因為對呼叫a+=[1]的block來說,他只是去呼叫a的一個member method。完成之後a與b還是指向同一個object。像a<<b就是這樣的例子。
另外,這些敘述應該是等價的:
- Python的a+=[b]
- Python的a.append(b)
- Ruby的a<<b
- Ruby的a.push b
我個人是Python和Ruby都有在用,在這個小地方上我比較喜歡Python的作法:將+=視為member function,operator的left operand所綁定的目標不會改變。知道這點的話,Python應該是易了解又有一致性。