["+", ["*", 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)相关推荐

  1. python如何用c语言表示_如何在python中调用C语言代码

    原博文 2019-04-03 14:19 − 1.使用C扩展CPython还为开发者实现了一个有趣的特性,使用Python可以轻松调用C代码 开发者有三种方法可以在自己的Python代码中来调用C编写 ...

  2. 小结两种在Python中导入C语言扩展库的方法

    小结两种在Python中导入C语言扩展库的方法 分类: Pythoner2009-08-18 20:44 2563人阅读 评论(1) 收藏 举报 python扩展c语言importstring 一种是 ...

  3. python r语言 结合 部署_(转)python中调用R语言通过rpy2 进行交互安装配置详解...

    python中调用R语言通过rpy2 进行详解 1.R语言的安装: 大家进行R语言的安装,在安装好R后,需要配置环境变量R才能进行使用. 对此电脑右键->选择高级设置->环境变量-> ...

  4. python调用r语言加载包错误_Python中调用R语言包指南.docx

    Python中调用R语言包指南R语言是非常强大的做统计分析和建模方面的开源软件,它有非常丰富的统计软件包,做统计可以说只有你想不到的,没有R办不到的.Python又是当下最流行的编程软件之一,Pyth ...

  5. python写游戏与c语言转化,使用ctypes实现python类型和C语言类型之间的相互转化

    楔子 我们知道可以使用ctypes调用扩展模块,主要是调用扩展模块中使用C编写好的函数,但这些函数肯定都是需要参数的,还有返回值,不然编写扩展模块有啥用呢.那么问题来了,不同的语言变量类型不同,所以p ...

  6. c语言金字塔输出乘法表,python中打印金字塔和九九乘法表的几种方法

    # 打印九九乘法表 for i in range(1,10): for j in range(1,i+1): # x=i*j # print(i,'*',j,'=',x,end=' ') print( ...

  7. pyqt5生成py的文件为什么是c 语言,如何使用PyQt5在python中创建文件对话框

    我有一个名为PDFviewer的python类,在运行该程序时,系统将显示一个窗口,该窗口处理button(打开文件夹),它将打开一个文件对话框,允许用户选择一个目录并显示其中的文件.在 问题是,当我 ...

  8. C语言大数阶乘取余,python中math模块常用函数介绍 取模(取余)取绝对值 求阶乘 求最大公约数最小公倍数 取对数 取根号 取幂(取次方) 取整函数 三角函数与反三角函数...

    前提:import math 两个常用常量 e = 2.718281828459045 pi = 3.141592653589793 >>> import math >> ...

  9. python语言特点强制可读-Python中文件的读写、写读和追加写读三种模式的特点

    本文主要讨论一下文件的三种可读可写模式的特点及互相之间的区别,以及能否实现修改文件的操作 由于前文已经讨论过编码的事情了,所以这里不再研究编码,所有打开操作默认都是utf-8编码(Linux系统下) ...

最新文章

  1. 文件时间信息在测试中的应用
  2. bgl 词典_器材屋 篇五十二:“哪里不会点哪里”的后时代——哪里不识扫哪里:科大讯飞扫描词典笔评测_点读机...
  3. 唐僧肉长生不老到底隐藏了哪些骗局?唐僧的妈妈真的吃过唐僧肉吗?
  4. 支持java虚拟主机_为何缺乏支持Java的虚拟主机
  5. mysql判断数字的函数_Mysql必读MySql判断汉字、日期、数字的具体函数
  6. python3.6+selenium_Testsuits测试套件
  7. 页面平滑过渡全屏切换
  8. 0046 @Transactional注解的几个参数--事务传播控制--事务隔离级别--异常与回滚
  9. (原創) 有限狀態機FSM coding style整理 (SOC) (Verilog)
  10. Linux学习笔记5 - Shell编程(类似于Windows下的批处理)
  11. 草图大师:SketchUp 2019 for Mac
  12. 最新Chrome插件开发 api 解析
  13. Spring源码解析(一)下载及编译(版本5.2.x)
  14. AuthorizingRealm中的两大方法
  15. CTO、首席架构师、技术总监、研发Leader、高级程序员的职责
  16. setpositivebutton
  17. 中山医06年考研初试复试全攻略!( 完整版)
  18. Closures in OOC
  19. caniuse_使用此工具将CanIUse表嵌入到您的网站中
  20. DVB数字电视系统简介(DVB-C,DVB-S,DVB-T)

热门文章

  1. pat basic 1071 小赌怡情
  2. vue 自定义生成表格 并且可以输入
  3. 了解Spring事务
  4. 诚之和:网络安全已成为最紧迫、最基础的安全问题,监管制度已在路上
  5. 计及新能源出力不确定性的电气设备综合能源系统协同优化(Matlab代码实现)
  6. Jetson TX1板载相机opencv调用打开
  7. LaTeX系列 |3、常用LaTeX表格模版
  8. 如何选择,没有方向,下面来看看马云说未来五年最赚钱的行业。
  9. 数据分析--matplotlib 设置时间数据的刻度格式
  10. 新版BIOS U盘启动装机