聊聊 print 的前世此生

本文原創並首發於公衆號【Python貓】,未經受權,請勿轉載。html

原文地址:https://mp.weixin.qq.com/s/NuzfuH_zCZzcrmSFR04NHwpython

(一)git

上週,我翻譯了一篇文章,解釋了爲何 Python 3 把 print 改成函數? 歸納有以下幾點緣由:一、print 不適宜做爲應用程序級的語句。二、改成一個函數,能夠實現更復雜的功能。三、改成一個函數,能方便地進行替換。github

在 Python 2 中,print 是個語句(statement),它的級別就跟 for、if、def 等關鍵字相同,這是一個古老的設計(畢竟 Python 誕生於 1989 年),改爲 print() 函數,意味着它升級了。編程

在查閱資料的時候,我發現 print 在歷代版本中,一直髮展變化,到了今天,它自身已足夠完善了,但是外部的挑戰一直不斷。服務器

所以,這篇文章再來聊聊它:介紹 print 的現狀,追溯它的歷史,說說它的挑戰者,挖挖那些更加本質的東西。網絡

(二)多線程

在 3.0 版本中,print() 函數全新登場,開發者能夠自定義打印對象的間隔(默認是空格)、終止方式(默認是換行)、以及輸出位置(默認是標準輸出 sys.stdout)。編程語言

而到了 3.3 版本,它還添加了一個新的參數,能夠決定是否要刷新數據流。函數

至此,這個函數的完整格式就變成了 print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False) ,與升級前的 print 語句是天壤之別啦。

優勢是顯而易見的,可定製的參數帶來了使用場景的擴充。

(三)

其實,在此次大版本的改動以前,早期的 print 語句並不是是一成不變的,核心開發者們一直在完善它。

例如,在 2000 年的 PEP-214 以前,print 語句只能用於標準輸出(sys.stdout),只有當提出這個提案後,print 才能夠打印內容到類文件對象(file-like object)中。

(注:PEP 即 Python 改進提案,更多介紹詳見舊文《學習Python,怎能不懂點PEP呢?》)

此次調整後,它的寫法能夠以下(其中,mylogfile 是用於記錄打印信息的文件路徑):

print >> mylogfile, 'this message goes to my log file'

在只接觸過 Python 3 的同窗眼裏,這個寫法可能很彆扭吧,其實它等同於現在的:

print('this message goes to my log file', file = mylogfile)

(四)

上例是一次成功的改進,但有趣的是,社區內也有一次失敗的修改提案。

與 print() 函數相同,print 語句在打印完一個對象後,默認會換行,所以,當打印的內容自帶了換行符的時候,最終的打印結果就會出現一個多餘的換行。

2001 年的時候,有開發者在 PEP-259 中提議,根據打印的最後一個字符的類型,設置幾個標誌位,以此決定是否要默認換行。校驗規則以下:

  • -1 ——若是最後一個對象是以換行符結束的字符串
  • 0 ——若是最後一個對象是以空白字符結尾的字符串,既不是空格也不是換行符
  • 1 ——在全部其它狀況下(包括最後一個對象是空字符串或不是字符串的狀況)

根據這些規則,print 語句遇到 -1 標誌位的時候,就再也不作默認的換行了,彷佛能夠解決多餘換行的問題。

然而,這個提案被否決了。反對的意見主要是:這樣可能會破壞掉無數個 CGI 腳本,並且 Python 中已經有太多的「魔法」了。

這一套規則確實太神奇了,幸虧沒有實施。在當前的版本中,只需調整 end 參數,就能夠避免多餘換行的問題。

(五)

閱讀過往的 PEP 文檔,就是在閱讀 Python 的歷史,從中你能夠看到設計者們對功能細節的打磨過程,最終你就明白了,Python 是如何一步一步地發展成今天的樣子。

不過,歷史中除了能看到精華,也能夠看到一些包袱。print() 函數的升級就是在甩掉包袱,前不久我寫了《聊聊 Python 的內置電池》,聊到了 Python 中廢棄部分標準庫的話題,也是一個很好的觀察例子。

除此以外,「print」的命名自己也算是一種包袱。

早期的計算機使用紙帶做爲信息載體,程序的運算結果須要 print 在紙帶上,因此瓜熟蒂落地,有些編程語言就使用了「print」來表示程序的輸出操做。儘管後來再也不使用紙帶了,一些語言仍然延用這個詞,例如 C 語言以及借鑑了 C 語言的 Python。

Python 的另外一個借鑑對象是 Shell,這是一種古老的腳本語言,可它沒有「print」的包袱,它用的是 echo。這個詞的本意是回聲,後來也指雷達的回波,被用於計算機編程中,則又被賦予了「應答、回顯」之義,更直白的表述應該是「輸出、打印」。

Python 從 C 中借用了「print」命名,又從 Shell 中借用語句式的表達,造成了本身 print 語句,現在到了新的版本,它去除了語句式的表達,卻仍保留着原始命名,能夠說這個包袱是永遠脫不掉了。

可是,話說回來,詞語在演化過程當中會得到新的生命,它的意義全在於如何使用。因此,雖然沒有了紙帶這個物理載體,print 這個詞卻「改頭換面」地活了下來。

它還擁有不少的表兄弟姐妹呢,很是熱鬧(試試你能認出幾個?):

print("點個贊吧!")
printf("點個贊吧!");
print_r('點個贊吧!');
var_dump('點個贊吧!');
NSLog(@"點個贊吧!");
System.out.println("點個贊吧!");
console.log("點個贊吧!");
cout << "點個贊吧!" << endl;
Console.WriteLine("點個贊吧!");
writeln('點個贊吧!')
fmt.Println("點個贊吧!");
Response.Write("點個贊吧!");
alert("點個贊吧!")
echo "點個贊吧!"
puts "點個贊吧!"
say "點個贊吧!";

(六)

語言內部的發展歷史,以及不一樣語言的類似表述,都代表着一件事,那就是打印操做很重要,並且咱們對它的要求還很複雜多樣。

Python 中的 print 語句能發展成今天的 print() 函數,已經很是完善了。

不過,需求是無止境的,做爲最經常使用的調試手段,print() 還達不到十全十美。它的好處是簡單直白、容易上手,但缺點則是功能單1、效率較低,在須要定製格式的頻繁使用場景下,不堪大用。

這在不一樣編程語言中是通病,所以你們都默契地提供了用於調試的日誌模塊,例如 Java 的 log4j,C++ 的 log4cxx,固然還有 Python 的 logging。

日誌模塊 logging 能夠說是對 print() 函數的替代式升級,主要優勢是更加靈活高效,例如能夠設置不一樣的日誌等級、配置多樣的格式化信息、甚至能夠輸出日誌到遠程服務器上。

固然,日誌模塊只是一種解決方案,也並非最完美的。

在 Python 中還有一些模塊能夠用於調試,例如最主流的 pdb,它能夠設置斷點、分步調試、查看棧片斷、動態調值等,用得好,有奇效。主流的 IDE 工具也都提供了一些調試手段,相比於簡單的 print(),它們具備降維打擊的優點。

今年 4 月,Github 上開源了一個專用於調試程序的庫,名叫 PySnooper ,短短兩個月,它就收穫了近 12K 個關注。這個三方庫的口號是「Never use print for debugging again」,其目標就是在調試代碼時徹底替代 print。

這個庫的用法很是簡單,只需一行代碼,就能夠實現對整個函數的監聽,作到記錄每一行的執行時間、記錄每一個變量的賦值等等,並且還可使用「with」語句,監聽部分的代碼塊,或者使用「watch」命令,專門監聽特定的變量值。

這個庫強大而驚豔,除了上述做用,它還能監聽指定格式開頭的代碼,能在多線程中監聽線程,甚至支持用戶自定義的監聽規則。難怪它一經面世,就好評如潮,人人奔走相告。

snoop 這個單詞頗有意思,它指的是嗅探、窺探和監聽。首字母大寫的 Snoop ,譯做史努比,則是一隻被不少人喜好的漫畫小狗。因此這個 PySnooper 庫就令我不禁地產生了一種聯想:它是一隻嗅覺異常敏銳的小狗,明白無誤地爲你執行各類監放任務。

史努比小狗(圖片來源於網絡)

(七)

最後,咱們能夠來回顧一下 print 的發展歷史了,有兩條線索,一條是它自身發展的明線,另外一條是它的挑戰者們的暗線。

先看明線吧,早期版本的 print 語句帶有 C 和 Shell 的影子,它是個應用程序級的 statement,使用十幾年間,有過一些改進的嘗試,例如 PEP-214 和 PEP-259;到了 2009 年的大版本 3.0,Python 把 print 語句改爲了 print() 函數,使它成爲了衆多內置函數的一員,隨後在 3.3 版本,又對它作了一次功能加強,至此,它完成了本身的華麗蛻變,佔據了穩固的一席之地。

至於暗線,print 的競爭對手們可謂衆多,像傳統的日誌模塊 logging、調試模塊 pdb、以及主流 IDE 的調試功能,等等,現在還有一位後起之秀 PySnooper,無不瞄準了 print 的位置,摩拳擦掌,虎視眈眈。

print 一詞最先應該跟紙帶相關,用途和需求場景都不多,現在的計算機世界已經不可同日而語,因此才促進了 print 自身的發展,也刺激了衆多對手們的崛起。

print 表明了一種訴求/思想:輸出計算結果、記錄程序過程、監察對象變化,而後用於查看、分析、調試、展現等等。

明線上的發展,就是繼承了它的名字,壯大 print;暗線上的發展,則是繼承了它的思想,爲了實現目的,各施手段,百花齊放。

print 固然不是 Python 所特有的,這明暗兩線的發展也同理,若是你把視野放到任何一個經得起時間考驗的語言上,必然也會看到類似的發展歷程與競爭故事。

最後,若是你想了解更多內容,可經過如下連接查看:

https://docs.python.org/3/library/functions.html#print

學習Python,怎能不懂點PEP呢?

https://www.python.org/dev/peps/pep-0214/

https://www.python.org/dev/peps/pep-0259/

爲何 Python 3 把 print 改成函數?

https://github.com/cool-RR/PySnooper

公衆號【Python貓】, 本號連載優質的系列文章,有喵星哲學貓系列、Python進階系列、好書推薦系列、技術寫做、優質英文推薦與翻譯等等,歡迎關注哦。

相關文章
相關標籤/搜索