為什麼 Python、Ruby 等語言棄用了自增運算符?
如題,為什麼在 Java 和 C++ 中使用的 ++、-- 運算符在 Ruby、Python 中無一例外地被剔除了?是因為太容易引起歧義了嗎?
每次打 i += 1 的時候都會想這個問題……
在 Python 中,整數是一種不可變類型。在創建對象之後,其值就不能再被改變。
在 Java 或 C 等語言中, ++ 做的事情是讓這個變數的值 +1,比如 a = 1; a++,意思是將 a 原本的內存區域表示的值替換為原本的值 +1。而在 Python 中,一旦一個整數對象創建,其值就不能再被改變。所以,就無法實現 自增、自減 操作了。
i += 1:
6 LOAD_FAST 0 (a)
9 LOAD_CONST 1 (1)
12 INPLACE_ADD
13 STORE_FAST 0 (a)
i = i + 1
6 LOAD_FAST 0 (a)
9 LOAD_CONST 1 (1)
12 BINARY_ADD
13 STORE_FAST 0 (a)
可以看到,在底層調用上還是不同的。反應到 Python 語言層面的實現上,__add__ 對應著 + 號運算,而 __iadd__ 對應著 += 運算。
以py為例如果要實現也很容易,不過需要注意,跟賦值一樣,在py中自增自減得是一個語句,而並不能成為其他表達式的一部分,能連續賦值是因為py有特殊支持這種語法,就像樓上一個答案說的,a=b=c不代表a=(b=c)另外有些答案說,py中a+=b相當於a=a+b,其實這也是不對的,比如list的+=相當於extend,而list的+是生成了新對象,python實際上是這樣實現的:a=a+b &<===&> a=a.__add__(b)a+=b &<===&> a=a.__iadd__(b)
可以看到是不同的運算符內建方法,完全可以實現為不一樣的邏輯(比如整數的__iadd__就直接調用__add__,所以等價,而list的則實現不同,且__iadd__裡面return的是self,於是就跟extend一樣了,只是多一次多餘的賦值,因為這個賦值是為了保證實現模型一致)
所以自增自減其實很容易實現:++a &<===&> a=a.__inc__()--a &<===&> a=a.__dec__()__inc__和__dec__跟上面說的一樣,return self即可,當然也可以不return self,這樣自由度更大,比如一個變數a在自增過程中出現一些情況,結果變成None了都可以因為作為語句,所以前++還是後++沒啥區別,當然py沒有提供這倆,可能是作者不喜歡,也可能是其他原因,總之理論上如果有了上面的限定,實現這倆本身沒啥難度stackoverflow.com: Why doesn"t Ruby support i++ or i-- (increment/decrement operators)?
Here is how Matz explains it in an old thread:
Hi,
In message "[ruby-talk:02706] X++?"
on 00/05/10, Aleksi Niemel? & writes:|I got an idea from http://www.pragprog.com:8080/rubyfaq/rubyfaq-5.html#ss5.3
|and thought to try. I didn"t manage to make "auto(in|de)crement" working so
|could somebody help here? Does this contain some errors or is the idea
|wrong?(1) ++ and -- are NOT reserved operator in Ruby.
(2) C"s increment/decrement operators are in fact hidden assignment.
They affect variables, not objects. You cannot accomplish
assignment via method. Ruby uses +=/-= operator instead.(3) self cannot be a target of assignment. In addition, altering
the value of integer 1 might cause severe confusion throughout
the program.matz.
無腦猜測
是為了強制你不寫出 a = b++ + ++c 之類的代碼。
你們不如反過來問,為什麼C/C++要設計++、--操作符。按我理解,設計這個操作符的全部的理由都在於把這個語法寫進while和for裡面:
for(int i = 0; i &< 100; i++){...}
for(vector&
while(--c){...}
while(is_succ(sendchunk(data[n++]))){...}
while(*(cstr++)){...}
尤其是for,C/C++的for是非常廣義的,甚至可以完全取代while,我們經常會看到一個循環把所有代碼都擠到了for的三個子語句中,然後一個分號結束。C/C++程序員的時間很寶貴,不會浪費在照顧Java程序員腦容量這種事情上,而且對於熟練的C/C++程序員來說,這種語法才是最可讀的,一眼就能看到兩件事:
- 我們重複地執行了一條語句
- 這條語句有幾個自增或者自減的自變數
自增和自減在C/C++的世界裡是非常重要的,數組的遍歷表現為指針或下標的遞增和遞減,因此C++的迭代器也必須使用遞增或者遞減操作符,因為要跟天然的迭代器——指針保持兼容。反觀Python等語言,for的邏輯是使用迭代器,連三段for的語法都沒有,也禁止使用賦值語句的結果來做條件判斷,自然也就不需要遞增遞減了。
《learning python》中的解釋是這樣的:
although Python now supports statements like X += Y, it still does not haveC』s auto-increment/decrement operators (e.g., X++, ??X). These don』t quite map to the Python object model because Python has no notion of in-place changes to immutable objects like numbers.個人理解,python中有諸如X+=Y的表達式是因為該表達式實際上又新創建了一個對象,並重新將引用X指向了該對象。而對於數值型的對象來說,因為數值型跟字元型等一樣,是不可變的,也就是上面說的,python中數值沒有自增的概念,因此X++表達式等也就沒有了意義。&>&>&> b = 5
&>&>&> a = 5
&>&>&> id(a)162334512&>&>&> id(b)162334512&>&>&> a is bTrue「可以看出, python 中,變數是以內容為基準而不是像 c 中以變數名為基準,所以只要你的數字內容是5,不管你起什麼名字,這個變數的 ID 是相同的,同時也就說明了 python 中一個變數可以以多個名稱訪問」-------上面這句並不正確。感謝 @la.onger指教,如他所說:int理論上是每次賦值都創建一個新對象的。但是由於使用頻繁,為了提升性能避免浪費,所有python有個 整數池,默認1~256的數字都屬於這個整數池,這些每次賦值的時候,是取得池中的整數對象。但是其他的除外,如:&>&>&> a = 257
&>&>&> b = 257&>&>&> id(a)140397570652784&>&>&> id(b)140397570652736「這樣的設計邏輯決定了 python 中數字類型的值是不可變的,因為如果如上例,a 和 b 都是 5,當你改變了 a 時,b 也會跟著變,這當然不是我們希望的」
-------這句也是錯誤的。改變a時,b並不會跟著改變,如 鄭程所說。如:
&>&>&> a=5
&>&>&> b=5
&>&>&> a+=1
&>&>&> a
6
&>&>&> b
5
原來的例子是錯誤的,感謝大家指教。關於本問題,原因應如kong kevin所說,+=代表改變了變數,而++代表改變了對象本身。而python的模型規定,數值對象是不可變的。所以++就沒有意義了。
關於 python 的自增運算
個人感覺應該是自增運算符沒有意義吧。因為Python里int類型的值其實是不可改變的,賦值只是將引用賦給了另一個對象。
#python 3
a = 2333
print("{}@{}".format(a, id(a)))
a += 1
print("{}@{}".format(a, id(a)))
可以看出雖然變數名仍然叫a,但在內存的地址卻已經改變了。對象都不是原來的對象了,何來自增之說?
相對的,在C++中//C++ 11
int a = 2333;
std::cout &<&< a &<&< "@" &<&< a &<&< std::endl;
++a;
std::cout &<&< a &<&< "@" &<&< a &<&< std::endl;
a += 1;
std::cout &<&< a &<&< "@" &<&< a &<&< std::endl;
a的地址是不會變的,程序將新值仍放在原來的地址中。不過,自增自減運算符也不是只能用於數值運算,C++中STL的iterator也可以用++/--,還是挺方便的,在引入Ranged for statement之前都要依賴這種方法來遍歷容器。
初學python,一點淺見還請指教!我也覺得用「++」,「--」等運算符多起來真的會把人繞暈,使開發人員花更多時間在這種沒什麼意義的問題上,浪費了大家的時間
In Python, the int data type is immutable—that is, once assigned, an int』s value cannot be changed. So, what actually happens behind the scenes when an augmented assignment operator is used on an immutable object is that the operation is performed, and an object holding the result is created; and then the target object reference is re-bound to refer to the result object rather than the object it referred to before. So, in the preceding case when the statement a+=8 is encountered, Python computes a+8, stores the result in a new int object, and then rebinds a to refer to this new int. (And if the original object a was referring to has no more object references referring to it, it will be scheduled for garbage collection.)---from programming in Python.by M. Summerfield在python中,變數都是對象的引用。int 類型的值是固定的,不可以改變。a+=2 的過程是,先計算a+2, 所得的結果存在一個新的int 對象,然後 a 再重新綁定為這個新的int 對象的引用。
lua中連 i += 1都沒有呀,想到這個,我覺得有i += 1已經很幸福了。
我覺得是為了提高代碼可讀性的需要,Ruby、Python的設計者才沒有為其添加自增和自減操作。
Python的設計哲學是「優雅」、「明確」、「簡單」。其設計目標之一是讓代碼具備高度的可閱讀性。像JavaScript等語言也不推薦使用++、--運算符。
相信很多學過C語言的人都被其自增運算符++和自減運算符--搞得頭疼不已。關於這些運算符的試題在面試中屢見不鮮。
其實在語法上出現自增自減運算符可能是不夠優雅的。作為一門注重效率的語言,C語言中出現自增自減操作可能是為了生成高效的機器碼,因為這些操作可以直接映射成CPU的指令。1、容易替代
n++
很容易被替代為:
n += 1
2、功能有限
只能實現自增1,想要自增2怎麼辦?還不是得寫成:
n += 2
所以乾脆去掉自增用+=替代算了
關於Ruby的這個特性,松本先生本人解釋過這個問題:
[ruby-list:126] Re: i++ ????我們先默認 i 是一個Integer。如果要在Ruby裡面實現 i++ 的話,按照Ruby的風格應該被理解成是『向 i 所指的對象發送++消息,該對象的值變為下一個值』。但是問題就在於整型在Ruby裡面並不是一個對象,而是一個立即值。也就是說 i 的值是直接存在這個變數裡面而不是先創建一個整型對象然後用 i 指向它(這樣應該是出於性能考慮)。這樣就無法用消息發送的模型來處理 i++ 這個動作了,所有和這類整型有關的讀寫操作都是另外實現的。
所以如果要加上 i++ 特性的話,要麼把整數對象化,但是這樣並沒有太大的意義,而且性能還可能會下降。要麼再給整數特地加上這個功能,但是這樣其他類型是不是也要加上++操作符呢(而且實現模型還不一樣,這是需要消息發送的)?但是大多數類型並不需要這個自增的語義(我能想到的就只有數字和迭代器可能會需要),為了這個特性修改語法解析規則帶來的收益太小,沒有意義。
況且和Python一樣,Ruby的數字是immutable的,i++這樣修改變數值的動作本來就是做不到的。好多人說是因為對象什麼的…
但i++可以當作i+=1的語法糖不是嗎…
主要是因為Python和Ruby下有超好用的迭代器導致自增沒那麼常用了吧…
順便知乎新版客戶端發個回答卡五次還差點給我全刪了……看了一下現有答案, 不大讚同 @kong kevin 的理解.
C 與 python 賦值實現上的差異並不是取消 "x++" 的原因.按照這種邏輯豈不是 python 連 "x += 1" 都不能實現了?python 中 "=" 賦值不是運算符, 也就是說 "x = 1" 並不是一個表達式.
然而在 C 中, "=" 作為一種運算符, 使得賦值能夠作為表達式的一部分.在 python 中, 下面的代碼是不合法的 y = (x = 1)
^
SyntaxError: invalid syntax
int x = 0;
int y = 0;
y = x = 1;
與
y = (x = 1);
是完全等價的
個人看來, 將運算與賦值兩種邏輯雜糅在一起是十分混亂的, python 也很自然的將兩者分離了開來, 自然 "x++" 這種形式在 python 中便不再存在.
BTW, 連等賦值在 python 中也是合法的, 但也只是單純的語法糖而已.最近在看一本書叫《Ruby程序員修鍊之道》,裡面說到在Ruby中的一些對象會被作為立即值儲存到變數中,比如整數就是這樣。當用整數給變數賦值時,變數持有這些值本身,而不是持有它們的引用。所以對這種整型變數使用自增運算符是不合理的,比如以下兩個語句:
x = 1
x++
由於x中的立即值是1,x++等同於1++,這意味著把1改變為2,這顯然不合理。
因為Python的哲學:
In [2]: import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren"t special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you"re Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it"s a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let"s do more of those!
道哥在《javascript 語言精粹》中說 javascript 中 ++ 容易造成內存泄漏,你可以順著這個思路查查資料
++本身不是一個好的設計
推薦閱讀:
※如何找到適合需求的 Python 庫?
※使用python語言如何保密源代碼以防止逆向工程?
※Python 3.x 上 str 與 bytes 轉換函數是什麼?
※看完廖雪峰的python,但是感覺自己掌握不紮實,不知道該怎麼做?
※用python的前輩們,pylab是matplotlib的一個模塊嗎,跟pyplot又是什麼關係呢?