這是一篇為Lisp程序員寫的Python簡介(一些Python程序員告訴 我,這篇文章對他們學習Lisp也有幫助,盡管這不是我的本意)。基本上,Python可以看作一個擁有“傳統”語法(Lisp社區稱之為“中綴”或者 “m-lisp”語法)的Lisp方言。一個來自comp.lang.python的帖子說到“我一直不明白為什麼LISP是一個不錯的想法,直到我開始 玩上了Python”。Python支持除了宏(macros)之外的所有Lisp的關鍵特征,並且你不會太想念宏因為Python擁有求值,運算符重載,和正則表達式解析,所以一些(不是所有)宏的使用場景都被涵蓋了。
我深入學習Python的原因是,我正在考慮把Russel & Norvig的人工智能教材配套的Lisp代碼轉化成Java代碼。一些教師和學生想要Java代碼,因為:
那是他們在其他課程中最熟悉的語言。
他們希望有圖形應用程序(graphical applications)。
少數人想要能在浏覽器中運行的applets。
一些人只是因為在他們可以投入的有限的上課時間內,不能習慣於Lisp的語法。
然而,我們編寫Java版本的第一次嘗試很不成功。Java太羅嗦了,並且書中的偽代碼和java代碼之間的差異太大。我環顧四周,尋找一種和書中偽代碼相近的語言,並最終發現 Python是最接近的。此外,有了Jython,我可以定位於Java的JVM。
對於我的使用目的而言,Python是一個非常優秀的語言。它使用簡單(交互式的,沒有“編譯-鏈接-加載-運行”循環),這點對於我的教學目的是很重要 的。雖然Python不滿足被拼寫為J-A-V-A的前提條件,但是Jython已經很接近了。對於沒有其他語言經驗的人而言,Python似乎比 Lisp更容易閱讀。我開發的Python代碼看起來比 Lisp代碼更像書中(獨立開發)的偽代碼。這點是很重要的,因為一些同學抱怨說,他們在把書中的偽代碼和網上的Lisp代碼對應起來的過程中,有一段困難的時間(即使這個過程對Lisp程序員來說是顯然的)。
從我的觀點來看,Python的兩個主要的缺點是:1)只有很少的編譯時的錯誤分析(compile-time error analysis)和類型聲明(type declaration),甚至比Lisp還少。2)運行時間比Lisp慢很多,通常相差10倍(有時100倍,有時1倍)。定性地說,Python和解 釋型的Lisp的速度差不多,但是很明顯地慢於編譯型的Lisp。基於這個原因,對於那些(或者會逐漸變為)計算密集性的應用(除非你願意把速度瓶頸部分 用c重寫),我不會推薦使用Python。但是我的目的是面向教育的,而不是產品,所以速度不是問題。
Python既可以看作一個實用(更好的庫)版本的Scheme,也可以看作一個淨化(沒有了“$@&%”符號)版本的Perl。然而Perl的 哲學是TIMTIWTDI(there's more than one way to do it,即都種方法解決同意問題),Python試圖提供一個最小子集,使得人們以同樣的方式使用它(即TOOWTDI,there's only one way to do it,但是如果你努力嘗試的話,通常會有多種方式去解決同一問題)。其中一個具有爭議的Python特征是,用縮進層次代替begin/end或者花括 號,啟發它的哲學思想是:因為這裡沒有括號,所以也就沒有了因為如何放置括號而引起的風格戰爭(style wars)。有趣的是,Lisp在這點上有同樣的哲學思想:每個人都用emacs去縮進代碼,所以他們沒有去爭論縮進方式。拿到一個Lisp代碼,對它進 行合理的縮進,刪除行首的左括號(opening parens),以及與之匹配的閉括號(close parens),這樣我們就得到看起來類似Python的程序。
Python有做出明智妥協的哲學思想,使得容易的事情更容易,並且不排除太多困難的事情。在我看來Python做的不錯。簡單的事情簡單,困難的事情逐 漸困難,並且你甚至注意不到這種不一致。Lisp有做出較少妥協的思想:提供一個強大並且完全一致的核心。這讓Lisp很難去學,因為你從一開始就操作在 一個較高的抽象層次上,而且你需要理解你正在做什麼,而不是僅僅依靠感覺活著看起來漂亮。但是它同樣意味著更容易為Lisp添加抽象層次和復雜 性;Lisp優化的目標是使得很困難的事情不太困難,而Python優化的目標是使得中等難度的事情更簡單。
這裡我從Python.org摘了一段對Python的簡介,並我創造了兩個版本:一個是用藍色斜體顯示的Python,另一個是用綠色粗體顯示的Lisp。其他大部分,兩個語言的共同部分,是黑色的。
Python/Lisp 是一個解釋型的和編譯型的, 面向對象的,高級編程語言,並且擁有動態語義。它的高級的內部數據結構,以及動態類型和動態綁定,使得它對於快速應用開發特別有吸引力,同樣地,Python作為腳本或者膠水語言去鏈接已存在的部分,也是非常有吸引力。Python/Lisp簡單、易學的語法強調的是可讀性,並因此降低程序維護的成本。Python/Lisp支持模塊(modules)和包(packages),這鼓勵了程序的模塊化和復用。Python/Lisp解釋器和廣泛的標准庫在大多數平台上都是以源碼或者二進制形式發布的,並且可以自由的散發。通常,程序員愛上Python/Lisp是因為它提供地不斷增加的生產力。因為這裡沒有分離的(separate)編譯步驟,“編輯-測試-調試”循環難以置信的快。調試Python/Lisp程序很簡單:一個bug或者壞輸入不會造成段錯誤。相反,當解釋器發現一個錯誤,它拋出一個異常。當程序沒有抓獲這個異常時,解釋器將打印棧軌跡。代碼級別的調試允許查看局部和全局變量,求值任意表達式,設置斷點,單步執行,等等。調試器是由Python/Lisp自己寫的,表明了Python/Lisp的自省能力。另一方面,通常最快的調試一個程序的方法是向源碼中添加一些打印語句:快速的“編輯-測試-調試”循環使得這個簡單的方法特別有效。
我只添加下面這句:
盡管有些人對於縮進作為塊結構/括號有原始的抵制,但是大多數人最後喜歡/深深的感激 他們。
想學習更多關於Python的知識,如果你是一個有經驗的程序員,我推薦你去Python.org的下載頁面,下載文檔包,並且特別要注意Python參考手冊和Python庫參考。這裡有各種各樣的輔導材料(tutorials)和出版的書籍,但是這些參考是你真正需要的。
下面的表作為一個Lisp/Python轉化的指南。紅色的表項標記了這個位置中的語言,明顯的比較糟糕(我自己的觀點)。粗體標記了這個位置上,兩個語言明顯不同,但是沒有一個方式明顯的好於另一個。用正常字體顯示的表項意味著兩個語言是相似的;語法可能稍有不同,但是概念是相同的,或者非常相近。表後面是一個要點列表和一些Python的示例程序.
關鍵特征 Lisp 特征 Python 特征 一切都是對象 是 是 對象有類型,變量沒有 是 是 支持異構的(heterogenous)列表 是 (linked list and array/vector) 是 (array) 多范式語言 是:函數式,命令式,OO,Generic 是:函數式,命令式,OO 存儲管理 自動垃圾收集 自動垃圾收集 包/模塊 使用困難 使用簡單 對象,類的自省 強大 強大 元編程的宏 強大的宏 沒有宏 交互式的REPL(Read-eval-print loop) > (string-append "hello" " " "world")對大多數人來說,一個重要的方面就是Python和Lisp相對於其他語言而言的執行速度如何。我很難得到和你的應用相關的基准測試數據,但是下面這個表可能對你有用:
The Great Computer Language Shootout5種語言在基准測試集上的相對速度.
對速度進行了規格化處理,以使得利用g++編譯器編譯過的c++代碼的速度是1.00,所以2.00意味著比c++慢2倍;0.01意味著比c++快 100倍。對於Lisp而言,使用的是CMUCL編譯器。背景顏色是按照右邊圖例說明進行繪制的。最後的3行給出了中位數值,25%到75%的 quartile值(即去掉兩個最低值和去掉兩個最高值),以及整體范圍。在去掉兩個最低的和兩個最高的之後,比較Lisp和Python,我們發現 Python比Lisp要慢3到85倍,Perl和Python差不多,但是比Java和Lisp都慢。Lisp大約比Java要快2倍
在這裡我列出了一些概念上的問題,是為像我一樣轉到Python的Lisp程序員准備的:
Lists are not Conses。 Python的列表其實比較像Lisp的可變數組,或者Java的向量(Vector)。這意味著列表的存取是O(1)的,但是與cons和cdr等價的 操作卻產生了O(n)的新空間。你真的應該使用map或者for e in x:,而不是基於car/cdr的遞歸。注意Python裡有多個空列表,而不是一個。這修正了Lisp一個常見的bug,即用戶調用(nconc old new),並期待old被修改了,但是當old是nil的時候,它並沒有被修改。在Python裡,即使old是[ ],old.extend(new)也可以正常工作。但是這將意味著你必須用==測試列表是否為[];而不是用is,同時這也意味著,如果你把一個默認參 數的值設置為[],你最好不要修改這個值。
Python is less functional。 部分原因是Python的列表不是conses,相比於Lisp,Python使用了更多方法去改變列表的結構,並且為了強調它們的改變,它們通常返回 None值。例如像list.sort, list.reverse, 和list.remove這樣的方法都是,但是Python的新版本中引入了函數式的版本,即作為一個函數而不是方法,我們現在有了sorted and reversed(但是沒有removed)。
Python classes are more functional.在Lisp(CLOS)中,當你重 定義一個類C時,表示類C的對象也相應地得到修改。已經存在的C的實例和子類也因此重新指向新的類。這有時候會引起一些問題,但是在交互式的調試中,這卻 是很有用的。在Python中,當你重定義一個類時,你會得到一個新的類對象,但是實例和子類還是指向舊類。這就意味著大多數時候你必須重新載入你的子 類,並且重建你的數據結構。如果你忘記了,將會引起混淆。
Python is more dynamic, does less error-checking. 在 Python中,對於未定義的函數或者域,或者傳給了函數錯誤的參數個數,或者其他載入階段的其他大多數問題,你都不會得到任何警告信息;你必須等到運行 時,才能得到錯誤信息。商業的Lisp���現將會把這些大多數問題標識為警告;簡單的Lisp實現(如clisp)不會。一個演示Python危險性的地方 是,當你想寫self.field = 0時,卻敲入了 self.feild = 0,後者將會動態的創建一個新的域。與之相對的Lisp等價物為,(setf (feild self) 0),它將給你一個錯誤。另一方面,訪問你一個未定義的域時,兩個語言都會報告一個錯誤。
Don't forget self.這點更應該引起Java程序員的注意:在一個方法中,確保你寫的是self.field,而不是field。這裡沒有隱式的作用域(scope)。通常這會引起一個運行時錯誤。這很令人討厭,但是我認為過一段時間後,人們將學會不這麼干。
不要忘記return.寫 函數def twice(x): x+x是很誘人的,並且這不會發出任何警告或者異常信號,但是你可能真正想要的是def twice(x): return x+x。這點特別令人厭煩,因為在一個lambda表達式中,return語句是被禁止的,但是它的語義卻是執行return。
注意單元素元組(tuple)。一 個元組是一個不可變的列表,並且用圓括號圍起來,而不是用方括號。()是空元組,(1, 2)是一個含有兩個元素的元組,但是(1)表示的是1。而一個元素的元組卻要用(1,)表示,我擦!Damian Morton指出,如果你把元組看成是由逗號(,)產生的,打印時帶有圓括號的數據,這樣就好理解了。圓括號只是起了消除歧義的作用,在這個解釋下,1, 2是含有兩個元素的元組,1,是含有一個元素的元組,圓括號有時是需要的,這主要取決於元組出現的位置。例如,雖然2, + 2,是一個合法的表達式,但是更加清晰的方式是(2,) + (2,)或者(2, 2)。
注意特定的異常 小心: 當key不存在時,dict[key]會拋出KeyError; lisp哈希表的用戶則期望得到nil。你應該捕獲這個異常或者用key in dict測試。
Python是Lisp-1的。 我的意思是Python只有一個命名空間,裡面包含了變量和函數,就像scheme一樣,而不是想Common Lisp那樣,有兩個命名空間。例如:
1 2 3def
f(
list
,
len
):
return
list
((
len
,
len
(
list
)))
## bad Python
(define (f
list
length) (
list
length (length
list
))) ;; bad Scheme
(defun f (
list
length) (
list
length (length
list
))) ;; legal Common Lisp
對於域和方法也是一樣:你不能提供一個和域名相同的方法名:
1 2 3class
C:
def
f(
self
):
return
self
.f
## bad Python
...
Python字符串不同於Lisp的符號(symbol)。Python通過把字符串內化到模塊或類的哈希表中,然後進行符號查找。也就是說,當你寫obj.slot時,Python在運行階段會去obj類的哈希表中查找字符串"slot"。Python也會內化一些用戶代碼中的字符串,例如,當你寫x = "str"時。但是Python並不內化那些看起來不像變量的字符串,例如x = "a str"(感謝Brian Spilsbur指出這點)。
Python沒有宏(macro)。 Python可以訪問一個程序的抽象語法樹,但是這不是適合“心髒不好”的人。從積極的方面來看,該模塊容易理解,並且在5分鐘內,我用5行代碼就可以得到:
1 2 3 4 5>>> parse(
"2 + 2"
)
[
'eval_input'
, [
'testlist'
, [
'test'
, [
'and_test'
, [
'not_test'
, [
'comparison'
,
[
'expr'
, [
'xor_expr'
, [
'and_expr'
, [
'shift_expr'
, [
'arith_expr'
, [
'term'
,
[
'factor'
, [
'power'
, [
'atom'
, [
2
,
'2'
]]]]], [
14
,
'+'
], [
'term'
, [
'factor'
,
[
'power'
, [
'atom'
, [
2
,
'2'
]]]]]]]]]]]]]]], [
4
, '
'], [0, '
']]
這令我相當失望,同樣的表達式在Lisp中的解析結果是(+ 2 2)。看來,只有真正的專家才會想去操縱Python的解析樹,相 反,Lisp的解析樹對任何人來說都是簡單可用。我們任然可以在Python中,通過連接字符串,來創建一些類似於宏的東西,但是它不能和其他語言集成, 所以在實踐中不這樣做。在Lisp中,兩個使用宏的主要目的是:新的控制結構和定制針對特定問題的語言。前者沒有在Python中實現。後者可以通過在 Python中,用適合特定問題的數據格式來做到:下面我在Python中定義了一個上下文無關語法,分別通過1)組合字典的內置語法,2)解析字符串的 預處理過程完成的。第一種方式和Lisp的宏一樣優雅。但是對於復雜的任務,例如為邏輯編程語言寫一個編譯器這樣的事,在Lisp中很容易,但是在 Python將很困難。
我從《Paradigms of Artificial Intelligence Programming》一書中取了一個簡單的隨機句子生產器程序,並把它翻譯成Python。結論:簡介性相當;Python因為grammar[phrase]比 (rule-rhs (assoc phrase *grammar*))簡單,而獲得一分,但是Lisp因為'(NP VP)比['NP', 'VP']更簡介而扳平比分。Python程序很可能比較低效,但是這不是我們關注的點。兩個語言看起來都很適合這樣的程序。調整浏覽器窗口到合適的寬度以便閱讀代碼。
Lisp程序 simple.lisp Python程序 simple.py(defparameter *grammar* '((sentence -> (noun-phrase verb-phrase)) (noun-phrase -> (Article Noun)) (verb-phrase -> (Verb noun-phrase)) (Article -> the a) (Noun -> man ball woman table) (Verb -> hit took saw liked)) "A grammar for a trivial subset of English.") (defun generate (phrase) "Generate a random sentence or phrase" (cond ((listp phrase) (mappend #'generate phrase)) ((rewrites phrase) (generate (random-elt (rewrites phrase)))) (t (list phrase)))) (defun generate-tree (phrase) "Generate a random sentence or phrase, with a complete parse tree." (cond ((listp phrase) (mapcar #'generate-tree phrase)) ((rewrites phrase) (cons phrase (generate-tree (random-elt (rewrites phrase))))) (t (list phrase)))) (defun mappend (fn list) "Append the results of calling fn on each element of list. Like mapcon, but uses append instead of nconc." (apply #'append (mapcar fn list))) (defun rule-rhs (rule) "The right hand side of a rule." (rest (rest rule))) (defun rewrites (category) "Return a list of the possible rewrites for this category." (rule-rhs (assoc category *grammar*)))
from random import choice grammar = dict( S = [['NP','VP']], NP = [['Art', 'N']], VP = [['V', 'NP']], Art = ['the', 'a'], N = ['man', 'ball', 'woman', 'table'], V = ['hit', 'took', 'saw', 'liked'] ) def generate(phrase): "Generate a random sentence or phrase" if isinstance(phrase, list): return mappend(generate, phrase) elif phrase in grammar: return generate(choice(grammar[phrase])) else: return [phrase] def generate_tree(phrase): """Generate a random sentence or phrase, with a complete parse tree.""" if isinstance(phrase, list): return map(generate_tree, phrase) elif phrase in grammar: return [phrase] + generate_tree(choice(grammar[phrase])) else: return [phrase] def mappend(fn, list): "Append the results of calling fn on each element of list." return reduce(lambda x,y: x+y, map(fn, list))
Running the Lisp Program Running the Python Program> (generate 'S) (the man saw the table)
>>> generate('S') ['the', 'man', 'saw', 'the', 'table']>>> ' '.join(generate('S')) 'the man saw the table'
Python中的grammer比Lisp中的丑陋,這讓我很擔心,所以我考慮在Python中寫一個解析器(後來發現已經有一些寫好的,並且可以免費獲得的),以及重載一些內置的操作符。第二種方法在一些應用中是可行的,例如我寫Expr class, 這是用來表現和操縱邏輯表達式的。但是對於這個應用而言,一個簡單、定制的語法規則解析器就夠了:一個語法規則是一個用“|”分開的,可選部分的列表,每 個可選部分都是由空格(" ")分隔的單詞列表。把grammar程序重寫為一個更加符合Python慣用法的程序,而不是Lisp程序的翻譯,下面就是該程序:
Python Program simple.py (idiomatic version)"""Generate random sentences from a grammar. The grammar consists of entries that can be written as S = 'NP VP | S and S', which gets translated to {'S': [['NP', 'VP'], ['S', 'and', 'S']]}, and means that one of the top-level lists will be chosen at random, and then each element of the second-level list will be rewritten; if it is not in the grammar it rewrites as itself. The functions rewrite and rewrite_tree take as input a list of symbols. The functions generate and generate_tree are convenient interfaces to rewrite and rewrite_tree that accept a string (which defaults to 'S') as input."""
import random
def make_grammar(**grammar):
"Create a dictionary mapping symbols to alternatives."
for (cat, rhs) in grammar.items():
grammar[cat] = [alt.split() for alt in rhs.split('|')] r
eturn grammar
grammar = make_grammar(
S = 'NP VP', NP = 'Art N', VP = 'V NP', Art = 'the | a', N = 'man | ball | woman | table', V = 'hit | took | saw | liked' )
def rewrite(symbols):
"Replace each non-terminal symbol in the list with a random entry in grammar (recursively)."
return [terminal for symbol in symbols
for terminal in (rewrite(random.choice(grammar[symbol]))
if symbol in grammar else [symbol])] def rewrite_tree(symbols): "Replace the list of symbols into a random tree, chosen from grammar."
return [{symbol: rewrite_tree(random.choice(grammar[symbol]))}
if symbol in grammar else symbol
for symbol in symbols] def generate(symbols='S'): """Replace symbol(s) in the space-delimited input string by a random entry in grammar (recursively until terminals); join back into a string."""
return ' '.join(rewrite(symbols.split())) def generate_tree(symbols='S'): "Replace symbol(s) in the space-delimited input string by a random entry in grammar (recursively until terminals);
return a tree.""" return rewrite_tree(symbols.split())
無需操作系統直接運行 Python 代碼 http://www.linuxidc.com/Linux/2015-05/117357.htm
CentOS上源碼安裝Python3.4 http://www.linuxidc.com/Linux/2015-01/111870.htm
《Python核心編程 第二版》.(Wesley J. Chun ).[高清PDF中文版] http://www.linuxidc.com/Linux/2013-06/85425.htm
《Python開發技術詳解》.( 周偉,宗傑).[高清PDF掃描版+隨書視頻+代碼] http://www.linuxidc.com/Linux/2013-11/92693.htm
Python腳本獲取Linux系統信息 http://www.linuxidc.com/Linux/2013-08/88531.htm
在Ubuntu下用Python搭建桌面算法交易研究環境 http://www.linuxidc.com/Linux/2013-11/92534.htm
Python 語言的發展簡史 http://www.linuxidc.com/Linux/2014-09/107206.htm