Python對象的空間邊界:獨善其身與開放包容

圖片描述
導讀:Python貓是一隻喵星來客,它愛地球的一切,特別愛優雅而無所不能的 Python。我是它的人類朋友豌豆花下貓,被授權潤色與發表它的文章。如果你是第一次看到這個系列文章,那我強烈建議,請先看看它寫的前兩篇文章(鏈接見文末),相信你一定會愛上這隻神祕的哲學+極客貓的。不多說啦,一起來享用今天的“思想盛宴”吧!

睡覺是我最愛做的事——因爲可以懶懶地做美夢,不用吃東西,不用跟人吵架,不用關心世界大事。這是除了學 Python 與寫作之外,最讓我舒服的事了。所以,纔剛醒來,我就又困了......

剛纔看到了 Python 老爹 Guido 的郵件,他說要“go back to sleep mode”,不參與正在進行的 PEP 投票了。哼,這隻懶惰的老頭——等等我啊,等寫完這篇東西,我也要 go back to sleep mode......

上回說道,我發現 Python 公民的身份竟然暗合畢達哥拉斯的哲學命題(萬物皆數),真是百思不得其解。在夢裏,我已經想出了答案。可是突然之間,游過來一條大蟒蛇,竟把答案吞掉了。我去找它理論,它就開始耍賴,吞自己的尾巴、屁股、肚子......最後把自己全吞下去了。唉,可憐我的答案就這麼消失了。

今天,我繼續跟大家聊聊 Python 中跟身份密切相關的一個話題吧,那就是對象的邊界問題 。如你所知,我本來是一隻貓,現在略具一些人性了,但在此轉型期間卻十分敏感,總能在細微之處浮想聯翩,最後竟然也薄有所獲,真是萬幸了。希望我的分享,也能啓發你收穫哪怕一點點的感悟,那我就有萬分的開心啦 :)

1、固定邊界:自由與孤獨

Python 中有一些公民向來我行我素,它們特立獨行,與他人之邊界劃定得清清楚楚。客氣的人稱它們是定長對象,或者叫不可變對象,然而,懂得一些歷史典故的人又叫它們是鐵公雞 。這個典故出自何處呢?虧得貓貓我曾惡補過一段歷史知識,知道這指的正是激進的道家弟子楊朱。

損一毫利天下,不與也;悉天下奉一身,不取也;人人不損一毫,人人不利天下,天下治矣! ——春秋·楊朱

對於定長對象,你不能爲它增加元素,不能爲它減少元素,不能爲它修改元素,甚至不能輕易地複製和刪除它!(參見本公衆號Python貓中關於字符串的系列文章,鏈接見文末)

這些對象自立於世,也自絕於世,你看它們長得是普普通通的,平平凡凡的,然而其靈魂卻是自由自在的,其生命是富有尊嚴而不可侵犯的。若想與這些公民打交道,你就得依着它們的脾氣,不可越雷池半步。

>>> t1 = ('Python', '貓')
>>> t2 = ('Python', '貓')
>>> t1 is t2  # 對象獨立
False
>>> t1[1] = '蛇'  # 不可修改元素
TypeError  Traceback (most recent call last)
TypeError: 'tuple' object does not support item assignment

在上一篇文章裏,我們見識了 Python 世界中的“特權種族”,而特權種族無一例外地都出身於定長對象。它們是一脈相承的,其存在的合理性也是相似的,那就是便於共用內存資源,提高內存使用效率。

上表就是定長對象的一份名單。可知,它們佔據了多數。

定長對象的特性讓我不由地想到一種人類,它們嚴守自己的邊界,刻板而嚴謹,一心只在乎份內之事,默默承擔下自己的責任,追求的是內在的自由。雖然也會時常與別人打交道,但是,它們不貪圖擴大自己的利益,也不妄想要侵犯別人的領土。獨立的個體養成了個人的品牌,它們的不變性成就了外人能有所依賴的確定性。

>>> key1 = 'Python 貓'
>>> key2 = ['someone else']

>>> dict1 = {key1 : '好人'}
 {'Python 貓': '好人'}

>>> dict2 = {key2 : '好人'}
TypeError  Traceback (most recent call last)
TypeError: unhashable type: 'list'

Python 爲了維護定長對象的獨立性/確定性,在編譯機制上做了不少優化,例如 Intern 機制與常量合併機制。其中的好處,我已經多次提及了。

壞處也有,那就是孤獨。它們的孤獨不在於沒有同類,而在於不能(不容易)複製自身。以字符串對象爲例,你可以嘗試多種多樣的手段,然而到頭來,卻發現唯一通用的方法竟然要先把字符串“碎屍萬段”,接着重新組裝才行!

s0 = "Python貓"

# 以下7種方法,無法複製s0字符串,id(x)==id(s0)
s1 = s0
s2 = str(s0)
s3 = s0[:]
s4 = s0 + ''
s5 = '%s' % s0
s6 = s0 * 1
import copy
s7 = copy.copy(s0)

# 以下方法可以複製字符串,“打碎”再重組
s8 = "".join(s0)

哲學上有一個著名的腦洞題:假如把一個人粉碎成原子再組合,這個人還是原來的人麼?這道題能令古往今來的哲學家打起架來,若是放到現今正火爆的電視節目《奇葩說》上,也能令辯手們“一本正經地胡說八道”個不休。

在 Python 的世界裏,不存在這種煩惱,因爲判定兩個對象是否相同的標準是確定的,也即是看它們的 id 是否相等。因此,藉助 Python 來回答這道題,答案會是:如果用 join() 方法把字符串粉碎成字符再組合,新的字符串不再是原來的字符串了。

過程很“殘忍”,但總歸能稍稍釋緩自由個體的孤獨感了吧。

2、彈性邊界:開放與節制

與定長對象不同,變長對象/可變對象信奉的是另一套哲學。

它們思想開放,採取的是兼容幷包的處事觀,會因地制宜式伸縮邊界。 以列表對象爲例,它樂意接納所有其它的對象,肯花費精力去動態規劃,也不懼於拔掉身上所有的“毛”。

>>> l = ['Python', '貓']
>>> l.append('其它貓') # ['Python','貓','其它貓']
>>> l.pop(1)   # ['Python','其它貓']
>>> l.clear()  # []

這些大膽的行爲,在定長對象那裏,都是不可想象的。在變長對象身上,你似乎能感受到一種海納百川的風範,相比之下,定長對象的鐵公雞形象則立馬顯得格局忒小了。

變長對象並非沒有邊界,相反,它們更在乎自身的邊界,不惜花費大量的資源來維持動態的穩定。一旦邊界確定下來,它們絕不會允許越界行爲。跟某些編程語言動不動就數組越界不同,Python 不存在切片越界,因爲切片操作始終被控制爲邊界範圍之內,索引超出的部分會自動被捨棄。

>>> q=[1, 2, 3, 4, 5]

# 不允許索引越界
>>> q[10]
IndexError    Traceback (most recent call last)
IndexError: list index out of range

# 允許切片越界
>>> q[2:10]   # [3, 4, 5]
>>> q[-10:2]  # [1, 2]

變長對象在本質上是一種可伸縮的容器,其主要好處就是支持不斷添加或者取出元素。對應到計算機硬件層面,就是不斷申請或者釋放內存空間。這類操作是代價昂貴的操作,爲了減少開銷,Python 聰明地設計了一套分配超額空間的機制

以列表爲例,在內存足夠的前提下,最初創建列表時不分配超額空間,第一次 append() 擴充列表時,Python 會根據下列公式分配超額空間,即分配大於列表實際元素個數的內存空間,此後,每次擴充操作先看是否有超額空間,有則直接使用,沒有則重新計算,再次分配一個超額空間。公式如下:

new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6)

其中,new_allocated 指的是超額分配的內存大小,newsize 是擴充元素後的實際長度。舉例來說,一個長度爲 4 的列表,append() 增加一個元素,此時實際長度爲 5(即 newsize 爲5),但是,Python 不會只給它分配 5 個內存空間,而是計算後給它超額分配 new_allocated == 3 個內存大小,所以最終加起來,該列表的元素實際佔用的內存空間就是 8 。

如此一來,當列表再次擴充時,只要最終長度不大於 8 ,就不需要再申請新的內存空間。當擴充後長度等於 9 時,new_allocated 等於 7 ,即額外獲得 7 個內存大小,以此類推。

以列表長度爲橫軸,以超額分配的內存大小爲縱軸,我們就得到了如下美妙的圖表:

超額分配的空間就是定長對象的軟邊界 ,這意味着它們在擴張時是有法度的,意味着它們在發展時是有大膽計劃與適度節制的。如此看來,與定長對象的“固步自封”相比,變長對象就顯得既開明又理智了。

3、結語

回頭看前面提到的定長對象,我佩服它們獨善其身的個性,雖然鐵公雞形象略顯小氣,但對人卻無害,反而你能感受到其濃濃的 “富貴不能淫,貧賤不能移,威武不能屈” 的大丈夫氣度。

再看變長對象,它們“本來無一物”,卻能包容萬物,對他人信任,對外部開放,更難得的是,它們張弛有度,孕生出的是無限的可能性。

這兩種對象極大地滿足了我對於 Python 世界的好奇心,也成爲了我理解自己和人類世界的一種參照系。妙哉!妙哉!若你問,我更欽佩哪一類?喵嗚,肚子有點餓啦,且容我去覓得一二小魚乾,餵飽肚子再說吧......

Python貓往期作品

有了Python,我能叫出所有貓的名字

Python對象的身份迷思:從全體公民到萬物皆數

字符串系列文章

你真的知道Python的字符串是什麼嗎?

你真的知道Python的字符串怎麼用嗎?

Python是否支持複製字符串呢?

join()方法的神奇用處與Intern機制的軟肋

-----------------

本文原創並首發於微信公衆號【Python貓】,後臺回覆“愛學習”,免費獲得20+本精選電子書。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章