lisp语言代替python_PLisp: 集成在Python中的LISP语言实现 (1)
["+", ["*", 3, 4], ['*', 5, 6]]
表示LISP的表達式:
(+ (* 3 4) (5 6))
又如,Python表達式:
["let",
[['x', 2],
['y', 3]],
['*', x, y]]
表示LISP的表達式:
(let
((x 2)
(y 3))
(* x y))
解釋的過程,就可以寫成一個Python函數,輸入一個Python表達式,輸出另一個Python表達式。中間可以用副作用來處理IO。
如何實現呢?
首先,求值需要一個符號表,這個任何語言都一樣,記錄變量(或者LISP的符號)對應的值。而LISP的符號表會隨著求值的過程(副作用)而改變。
class Context(UserDict):
def __init__(self, parent=None):
UserDict.__init__(self)
self.parent = parent
Context類表示一個符號表,或者說LISP求值的“上下文”。Context只不過是一個dict而已,輸入符號,對應到值。這個parent稍候解釋,因為context可以嵌套。
然后,每個LISP函數都可以用Python函數表示。定義為由“上下文和參數的笛卡爾積”到python值的映射。簡而言之,每個LISP函數對應的Python函數,除了“普通參數”以外,還要Context作為額外的參數。比如:
def add(context, one, another):
return one + another
# ['+', 1, 2] calls this function and evaluates to 3
這個函數和符號表無關,不讀取符號表,也不改變符號表。
又如:
def _set(context, key, value):
context[key] = value
return value
# ['set', 'x', 40] calls this function and sets symbol 'x' to 40
這是一個“賦值函數”,通過修改符號表,將符號key的對應于value的值。
而
def _print(context, expr):
print expr
return expr
# ['print', ['quote', 'hello world!']] calls this function and prints "hello world!" to standard output
這個完全依賴求值的副作用,打印輸出。
函數的基本形式就是這樣。
基本的LISP執行環境需要幾個基本的LISP函數。最基本的當然是eval函數了。LISP中,eval的功能是將一個表達式求值。我定義Python函數_eval,為了避免和內置函數eval命名沖突。
def _eval(context, expr): # 輸入expr,求expr的值
if isinstance(expr, str): # 對于符號,查找符號表獲得值
cur_context = context
# 因為符號表可以嵌套,所以迭代查找。
while cur_context is not None:
if expr in cur_context:
return cur_context[expr]
else:
cur_context = cur_context.parent
raise KeyError(expr)
elif isinstance(expr, list): # 對于表,轉換成函數調用。
first, rest = expr[0], expr[1:]
func = _eval(context, first) # 遞歸對第一個元素求值,得到函數。
if getattr(func,"call_by_name",False)==False: # 處理特殊函數。
evrest = [_eval(context, e) for e in rest]
else:
evrest = rest
return func(context, *evrest) # 調用函數,得到返回值
else:
return expr # 對于其他原子,返回其本身。
正好分3個分支,處理了LISP求值的3種情況。
上述代碼提到了“特殊函數”。一般的函數,調用前,要將參數求值,再傳入。如:
(+ (- 9 3) 4)
必須先求出(- 9 3)的值:6,才能傳入"+"函數。
但是,另外一些函數需要“按名傳遞”,即根據某些條件,選擇性地求值。如:
(if (eq (+ 1 1) 2) right wrong)
if函數是條件函數。先求第一個參數的值,如果是真,則求第二個參數的值,第三個參數不求值;如果是假,求第三個參數的值,第二個參數不動。
因此,對于這些特殊的LISP函數,需要在Python函數上做一些標記。我的做法是,在定義函數后,給這些函數設置其call_by_name的值為True。或者用@decorator更簡單。
典型的if函數,定義如下:
@call_by_name
def _if(context, condition, iftrue, iffalse=None):
if _eval(context, condition):
return _eval(context, iftrue)
else:
return _eval(context, iffalse)
@call_by_name內部完成call_by_name=True的賦值工作。而函數體內部,先用_eval函數求第一個參數的值,對于真假兩種情況,分別調用兩個分支。
另一個典型的“按名傳遞”的函數是quote,這個函數避免其內部的符號被求值。LISP中:
(quote abc)
'abc
兩者求值都得到符號abc。由于quote如此常用,因此LISP中有特殊的引號'語法,方便quote函數的應用。
在Python中:
@call_by_name
def quote(context, expr):
return expr
def q(expr):
return ['quote',expr]
quote函數不對expr求值,而直接返回expr。我還定義了Python函數q,類似LISP的',方便書寫。
有了以上基本函數,其實可以編一些簡單的程序了。以下是一個演示。
# 首先,使用之前,要實例化一個Context對象:
default_context = Context()
# 然后,將基本函數加入這個符號表
default_context["eval"] = _eval
default_context["print"] = _print
default_context["quote"] = quote
default_context["set"] = set
default_context["+"] = add
# 最后,用eval函數求值即可。
# 這是Hello world
_eval(default_context, ["print", q("Hello world!")])
# 屏幕上顯示Hello world!
# 賦值語句
_eval(default_context, ["set", q("x"), 5])
# 改變符號表,"x"對應整數5。
# 計算簡單的加法
result = _eval(default_context, ["+", ["+", 1, "x"], 3])
print result
# 輸出9
總結:
通過以上程序,可以看出,這種實現,輸入是完全合法的Python表達式,輸出也是Python表達式。求值僅僅是用Python語言做了Python表達式的處理而已。
根據目前定義的函數,這個“語言”功能還非常簡單;但是,下篇將引入更多函數,使得這個“語言”逐漸趨近于功能完備的程序設計語言。
分享到:
2010-04-20 02:21
瀏覽 3485
評論
總結
以上是生活随笔為你收集整理的lisp语言代替python_PLisp: 集成在Python中的LISP语言实现 (1)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: iphone11信号强度测试软件,信号差
- 下一篇: 使用Python转换PDF/Word/H