热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

pythonlisp_给Lisp程序员的Python简介

作者:PeterNorvig,译者:jineslong这是一篇为Lisp程序员写的Python简介(一些Python程序员告诉我ÿ

作者:Peter Norvig,译者:jineslong

这是一篇为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 简介

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")

"hello world"

>>> ' '.join(['hello', 'world'])

'hello world'

简介、富有表达力的语言

(defun transpose (m)

(apply #'mapcar #'list m))

> (transpose '((1 2 3) (4 5 6)))

((1 4) (2 5) (3 6))

def transpose (m):

return zip(*m)

>>> transpose([[1,2,3], [4,5,6]])

[(1, 4), (2, 5), (3, 6)]

跨平台可移植性

Windows, Mac, Unix, Gnu/Linux

Windows, Mac, Unix, Gnu/Linux

实现的数量

很多

一个主要的,附加一些分支(例如:Jython, Stackless)

开发模式

专有和开源

开源

效率

大约比C++慢1到2倍

大约比C++慢2到100倍

GUI, Web 等库

没有标准

GUI, Web 标准库

方法

方法分派

动态, (meth obj arg) 语法

runtime-type, multi-methods

动态, obj.meth(arg) 语法

runtime-type, single class-based

数据类型

Lisp 数据类型

Python 数据类型

Integer

Bignum

Float

Complex

String

Symbol

Hashtable/Dictionary

Function

Class

Instance

Stream

Boolean

Empty Sequence

Missing Value

Lisp List (linked)

Python List (adjustable array)

Others

42

100000000000000000

12.34

#C(1, 2)

"hello"

hello

(make-hash-table)

(lambda (x) (+ x x))

(defclass stack ...)

(make 'stack)

(open "file")

t, nil

(), #() linked list, array

nil

(1 2.0 "three")

(make-arrary 3 :adjustable t

:initial-contents '(1 2 3))

很多 (in core language)

42

100000000000000000

12.34

1 + 2J

"hello" or'hello' ## 不可变的

'hello'

{}

lambda x: x + x

class Stack: ...

Stack()

open("file")

True, False

(), [] tuple, array

None

(1, (2.0, ("three", None)))

[1, 2.0, "three"]

很多 (in libraries)

控制结构

Lisp 控制结构

Python 控制结构

语句和表达式

一切都是表达式

区分语句和表达式

假值(False)

nil是唯一的假值

False, None, 0, '', [ ], {}都是假值

函数调用

(func x y z)

func(x,y,z)

条件测试

(if x y z)

if x: y

else: z

条件表达式

(if x y z)

y if x else z

While循环

(loop while (test) do (f))

while test(): f()

其他循环

(dotimes (i n) (f i))

(loop for x in s do (f x))

(loop for (name addr salary) in db do ...)

for i in range(n): f(i)

for x in s: f(x) ## s为任意序列

for (name, addr, salary) in db: ...

赋值

(setq x y)

(psetq x 1 y 2)

(rotatef x y)

(setf (slot x) y)

(values 1 2 3) 在栈上

(multiple-value-setq (x y) (values 1 2))

x = y

x, y = 1, 2

x, y = y, x

x.slot = y

(1, 2, 3) 在堆中

x, y = 1, 2

异常

(assert (/= denom 0))

(unwind-protect (attempt) (recovery))

(catch 'ball ... (throw 'ball))

assert denom != 0, "denom != 0"

try: attempt()

finally: recovery()

try: ...; raise 'ball'

except 'ball': ...

其他控制结构

case, etypecase, cond, with-open-file,etc.

扩展的with语句

没有其他控制结构

词法结构

Lisp 词法结构

Python 词法结构

注释

;; 分号直到行尾

## 井号直到行尾

界定符(Delimiters)

用括号来界定表达式

(defun fact (n)

(if (<&#61; n 1) 1

(* n (fact (- n 1)))))

用缩进来界定语句

def fact (n):

if n <&#61; 1: return 1

else: return n * fact(n — 1)

高阶函数

Lisp 高阶函数

Python 高阶函数

应用函数

执行一个表达式

执行一个语句

加载文件

(apply fn args)

(eval &#39;(&#43; 2 2)) &#61;> 4

(eval &#39;(dolist (x list) (f x)))

(load "file.lisp") or (require &#39;file)

apply(fn, args) or fn(*args)

eval("2&#43;2") &#61;> 4

exec("for x in list: f(x)")

execfile("file.py") or import file

序列函数

(mapcar length &#39;("one" (2 3))) &#61;> (3 2)

(reduce #&#39;&#43; numbers)

(every #&#39;oddp &#39;(1 3 5)) &#61;> T

(some #&#39;oddp &#39;(1 2 3)) &#61;> 1

(remove-if-not #&#39;evenp numbers)

(reduce #&#39;min numbers)

map(len, ["one", [2, 3]]) &#61;> [3, 2]

or [len(x) for x in ["one", [2, 3]]]

reduce(operator.add, numbers)

all(x%2 for x in [1,3,5]) &#61;> True

any(x%2 for x in [1,2,3]) &#61;> True

filter(lambda x: x%2 &#61;&#61; 0, numbers)

or [x for x in numbers if x%2 &#61;&#61; 0]

min(numbers)

其他高阶函数

count-if 等

:test, :key 等关键字参数

没有其他内置的高阶函数

map/reduce/filter函数没有关键字参数

Close over read-only var

Close over writable var

(lambda (x) (f x y))

(lambda (x) (incf y x))

lambda x: f(x, y)

Can&#39;t be done; use objects

参数列表

Lisp 参数列表

Python 参数列表

可选参数

变长参数

未确定的关键字参数

调用约定

(defun f (&optional (arg val) ...)

(defun f (&rest arg) ...)

(defun f (&allow-other-keys &rest arg) ...)

只有明确声明时&#xff0c;才可以用关键词方式调用:

(defun f (&key x y) ...)

(f :y 1 :x 2)

def f (arg&#61;val): ...

def f (*arg): ...

def f (**arg): ...

用关键字方式调用任何函数:

def f (x,y): ...

f(y&#61;1, x&#61;2)

效率

Lisp 效率问题

Python 效率问题

编译

函数引用解析

声明

编译到本机代码

大多数“函数/方法”的查找很快

为了效率可以进行声明

编译到字节码

大多数“函数/方法”的查找比较慢

没有声明

特征

Lisp 特征和函数

Python 特征和函数

引用(Quotation)

引用整个列表结构:

&#39;hello

&#39;(this is a test)

&#39;(hello world (&#43; 2 2))

引用单个的字符串或者.split():

&#39;hello&#39;

&#39;this is a test&#39;.split()

[&#39;hello&#39;, &#39;world&#39;, [2, "&#43;", 2]]

自省(Introspectible)文档字符串

(defun f (x)

"compute f value"

...)

> (documentation &#39;f &#39;function)

"compute f value"

def f(x):

"compute f value"

...

>>> f.__doc__

"compute f value"

列表访问

通过函数:

(first list)

(setf (elt list n) val)

(first (last list))

(subseq list start end)

(subseq list start)

通过语法:

list[0]

list[n] &#61; val

list[-1]

list[start:end]

list[start:]

哈希表访问

通过函数:

(setq h (make-hash-table))

(setf (gethash "one" h) 1.0)

(gethash "one" h)

(let ((h (make-hash-table)))

(setf (gethash "one" h) 1)

(setf (gethash "two" h) 2)

h)

通过语法:

h &#61; {}

h["one"] &#61; 1.0

h["one"] or h.get("one")

h &#61; {"one": 1, "two": 2}

列表上的操作

(cons x y)

(car x)

(cdr x)

(equal x y)

(eq x y)

nil

(length seq)

(vector 1 2 3)

[x] &#43; y but O(n); also y.append(x)

x[0]

x[1:] but O(n)

x &#61;&#61; y

x is y

() or [ ]

len(seq)

(1, 2, 3)

数组上的操作

(make-array 10 :initial-element 42)

(aref x i)

(incf (aref x i))

(setf (aref x i) 0)

(length x)

#(10 20 30) 如果大小不变的话

10 * [42]

x[i]

x[i] &#43;&#61; 1

x[i] &#61; 0

len(x)

[10, 20, 30]

对大多数人来说&#xff0c;一个重要的方面就是Python和Lisp相对于其他语言而言的执行速度如何。我很难得到和你的应用相关的基准测试数据&#xff0c;但是下面这个表可能对你有用&#xff1a;

测试LispJavaPythonPerlC&#43;&#43;

哈希访问

1.06

3.23

4.01

1.85

1.00

异常处理

0.01

0.90

1.54

1.73

1.00

图例说明

对文件中的数字进行求和

7.54

2.63

8.34

2.49

1.00

> 100 x C&#43;&#43;

反转行

1.61

1.22

1.38

1.25

1.00

50-100 x C&#43;&#43;

矩阵相乘

3.30

8.90

278.00

226.00

1.00

10-50 x C&#43;&#43;

堆排序

1.67

7.00

84.42

75.67

1.00

5-10 xC&#43;&#43;

数组访问

1.75

6.83

141.08

127.25

1.00

2-5 x C&#43;&#43;

列表处理

0.93

20.47

20.33

11.27

1.00

1-2 x C&#43;&#43;

对象实例化

1.32

2.39

49.11

89.21

1.00

<1 x C&#43;&#43;

单词计数

0.73

4.61

2.57

1.64

1.00

中位数

1.67

4.61

20.33

11.27

1.00

25% to 75%

0.93 to 1.67

2.63 to 7.00

2.57 to 84.42

1.73 to 89.21

1.00 to 1.00

范围

0.01 to 7.54

0.90 to 20.47

1.38 to 278

1.25 to 226

1.00 to 1.00

对速度进行了规格化处理&#xff0c;以使得利用g&#43;&#43;编译器编译过的c&#43;&#43;代码的速度是1.00&#xff0c;所以2.00意味着比c&#43;&#43;慢2倍&#xff1b;0.01意味着比c&#43;&#43;快100倍。对于Lisp而言&#xff0c;使用的是CMUCL编译器。背景颜色是按照右边图例说明进行绘制的。最后的3行给出了中位数值&#xff0c;25%到75%的quartile值(即去掉两个最低值和去掉两个最高值)&#xff0c;以及整体范围。在去掉两个最低的和两个最高的之后&#xff0c;比较Lisp和Python&#xff0c;我们发现Python比Lisp要慢3到85倍&#xff0c;Perl和Python差不多&#xff0c;但是比Java和Lisp都慢。Lisp大约比Java要快2倍。

给Lisp程序员的Python要点

在这里我列出了一些概念上的问题&#xff0c;是为像我一样转到Python的Lisp程序员准备的&#xff1a;

Lists are not Conses。 Python的列表其实比较像Lisp的可变数组&#xff0c;或者Java的向量(Vector)。这意味着列表的存取是O(1)的&#xff0c;但是与cons和cdr等价的操作却产生了O(n)的新空间。你真的应该使用map或者for e in x:&#xff0c;而不是基于car/cdr的递归。注意Python里有多个空列表&#xff0c;而不是一个。这修正了Lisp一个常见的bug&#xff0c;即用户调用(nconc old new)&#xff0c;并期待old被修改了&#xff0c;但是当old是nil的时候&#xff0c;它并没有被修改。在Python里&#xff0c;即使old是[ ]&#xff0c;old.extend(new)也可以正常工作。但是这将意味着你必须用&#61;&#61;测试列表是否为[]&#xff1b;而不是用is&#xff0c;同时这也意味着&#xff0c;如果你把一个默认参数的值设置为[]&#xff0c;你最好不要修改这个值。

Python is less functional。 部分原因是Python的列表不是conses&#xff0c;相比于Lisp&#xff0c;Python使用了更多方法去改变列表的结构&#xff0c;并且为了强调它们的改变&#xff0c;它们通常返回None值。例如像list.sort,list.reverse, 和list.remove这样的方法都是&#xff0c;但是Python的新版本中引入了函数式的版本&#xff0c;即作为一个函数而不是方法&#xff0c;我们现在有了sorted andreversed(但是没有removed)。

Python classes are more functional.在Lisp(CLOS)中&#xff0c;当你重定义一个类C时&#xff0c;表示类C的对象也相应地得到修改。已经存在的C的实例和子类也因此重新指向新的类。这有时候会引起一些问题&#xff0c;但是在交互式的调试中&#xff0c;这却是很有用的。在Python中&#xff0c;当你重定义一个类时&#xff0c;你会得到一个新的类对象&#xff0c;但是实例和子类还是指向旧类。这就意味着大多数时候你必须重新载入你的子类&#xff0c;并且重建你的数据结构。如果你忘记了&#xff0c;将会引起混淆。

Python is more dynamic, does less error-checking.在Python中&#xff0c;对于未定义的函数或者域&#xff0c;或者传给了函数错误的参数个数&#xff0c;或者其他载入阶段的其他大多数问题&#xff0c;你都不会得到任何警告信息&#xff1b;你必须等到运行时&#xff0c;才能得到错误信息。商业的Lisp实现将会把这些大多数问题标识为警告&#xff1b;简单的Lisp实现(如clisp)不会。一个演示Python危险性的地方是&#xff0c;当你想写self.field &#61; 0时&#xff0c;却敲入了self.feild &#61; 0&#xff0c;后者将会动态的创建一个新的域。与之相对的Lisp等价物为&#xff0c;(setf (feild self) 0)&#xff0c;它将给你一个错误。另一方面&#xff0c;访问你一个未定义的域时&#xff0c;两个语言都会报告一个错误。

Don&#39;t forget self.这点更应该引起Java程序员的注意&#xff1a;在一个方法中&#xff0c;确保你写的是self.field&#xff0c;而不是field。这里没有隐式的作用域(scope)。通常这会引起一个运行时错误。这很令人讨厌&#xff0c;但是我认为过一段时间后&#xff0c;人们将学会不这么干。

不要忘记return.写函数def twice(x): x&#43;x是很诱人的&#xff0c;并且这不会发出任何警告或者异常信号&#xff0c;但是你可能真正想要的是def twice(x): return x&#43;x。这点特别令人厌烦&#xff0c;因为在一个lambda表达式中&#xff0c;return语句是被禁止的&#xff0c;但是它的语义却是执行return。

注意单元素元组(tuple)。一个元组是一个不可变的列表&#xff0c;并且用圆括号围起来&#xff0c;而不是用方括号。()是空元组&#xff0c;(1, 2)是一个含有两个元素的元组&#xff0c;但是(1)表示的是1。而一个元素的元组却要用(1,)表示&#xff0c;我擦&#xff01;Damian Morton指出&#xff0c;如果你把元组看成是由逗号(,)产生的&#xff0c;打印时带有圆括号的数据&#xff0c;这样就好理解了。圆括号只是起了消除歧义的作用&#xff0c;在这个解释下&#xff0c;1, 2是含有两个元素的元组&#xff0c;1,是含有一个元素的元组&#xff0c;圆括号有时是需要的&#xff0c;这主要取决于元组出现的位置。例如&#xff0c;虽然2, &#43; 2,是一个合法的表达式&#xff0c;但是更加清晰的方式是(2,) &#43; (2,)或者(2, 2)。

注意特定的异常 小心: 当key不存在时&#xff0c;dict[key]会抛出KeyError; lisp哈希表的用户则期望得到nil。你应该捕获这个异常或者用key in dict测试。

Python是Lisp-1的。 我的意思是Python只有一个命名空间&#xff0c;里面包含了变量和函数&#xff0c;就像scheme一样&#xff0c;而不是想Common Lisp那样&#xff0c;有两个命名空间。例如&#xff1a;

def 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

对于域和方法也是一样&#xff1a;你不能提供一个和域名相同的方法名&#xff1a;

class C:

def f(self): return self.f ## bad Python

...

Python字符串不同于Lisp的符号(symbol)。Python通过把字符串内化到模块或类的哈希表中&#xff0c;然后进行符号查找。也就是说&#xff0c;当你写obj.slot时&#xff0c;Python在运行阶段会去obj类的哈希表中查找字符串"slot"。Python也会内化一些用户代码中的字符串&#xff0c;例如&#xff0c;当你写x &#61; "str"时。但是Python并不内化那些看起来不像变量的字符串&#xff0c;例如x &#61; "a str"(感谢Brian Spilsbur指出这点)。

Python没有宏(macro)。 Python可以访问一个程序的抽象语法树&#xff0c;但是这不是适合“心脏不好”的人。从积极的方面来看&#xff0c;该模块容易理解&#xff0c;并且在5分钟内&#xff0c;我用5行代码就可以得到&#xff1a;

>>> parse("2 &#43; 2")

[&#39;eval_input&#39;, [&#39;testlist&#39;, [&#39;test&#39;, [&#39;and_test&#39;, [&#39;not_test&#39;, [&#39;comparison&#39;,

[&#39;expr&#39;, [&#39;xor_expr&#39;, [&#39;and_expr&#39;, [&#39;shift_expr&#39;, [&#39;arith_expr&#39;, [&#39;term&#39;,

[&#39;factor&#39;, [&#39;power&#39;, [&#39;atom&#39;, [2, &#39;2&#39;]]]]], [14, &#39;&#43;&#39;], [&#39;term&#39;, [&#39;factor&#39;,

[&#39;power&#39;, [&#39;atom&#39;, [2, &#39;2&#39;]]]]]]]]]]]]]]], [4, &#39;&#39;], [0, &#39;&#39;]]

这令我相当失望&#xff0c;同样的表达式在Lisp中的解析结果是(&#43; 2 2)。看来&#xff0c;只有真正的专家才会想去操纵Python的解析树&#xff0c;相反&#xff0c;Lisp的解析树对任何人来说都是简单可用。我们任然可以在Python中&#xff0c;通过连接字符串&#xff0c;来创建一些类似于宏的东西&#xff0c;但是它不能和其他语言集成&#xff0c;所以在实践中不这样做。在Lisp中&#xff0c;两个使用宏的主要目的是&#xff1a;新的控制结构和定制针对特定问题的语言。前者没有在Python中实现。后者可以通过在Python中&#xff0c;用适合特定问题的数据格式来做到&#xff1a;下面我在Python中定义了一个上下文无关语法&#xff0c;分别通过1)组合字典的内置语法&#xff0c;2)解析字符串的预处理过程完成的。第一种方式和Lisp的宏一样优雅。但是对于复杂的任务&#xff0c;例如为逻辑编程语言写一个编译器这样的事&#xff0c;在Lisp中很容易&#xff0c;但是在Python将很困难。

比较Lisp和Python程序

我从《Paradigms of Artificial Intelligence Programming》一书中取了一个简单的随机句子生产器程序&#xff0c;并把它翻译成Python。结论&#xff1a;简介性相当&#xff1b;Python因为grammar[phrase]比(rule-rhs (assoc phrase *grammar*))简单&#xff0c;而获得一分&#xff0c;但是Lisp因为&#39;(NP VP)比[&#39;NP&#39;, &#39;VP&#39;]更简介而扳平比分。Python程序很可能比较低效&#xff0c;但是这不是我们关注的点。两个语言看起来都很适合这样的程序。调整浏览器窗口到合适的宽度以便阅读代码。

Lisp程序 simple.lisp

Python程序 simple.py

(defparameter *grammar*

&#39;((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 #&#39;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 #&#39;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 #&#39;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 &#61; dict(

S &#61; [[&#39;NP&#39;,&#39;VP&#39;]],

NP &#61; [[&#39;Art&#39;, &#39;N&#39;]],

VP &#61; [[&#39;V&#39;, &#39;NP&#39;]],

Art &#61; [&#39;the&#39;, &#39;a&#39;],

N &#61; [&#39;man&#39;, &#39;ball&#39;, &#39;woman&#39;, &#39;table&#39;],

V &#61; [&#39;hit&#39;, &#39;took&#39;, &#39;saw&#39;, &#39;liked&#39;]

)

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] &#43; 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&#43;y, map(fn, list))

Running the Lisp Program

Running the Python Program

> (generate &#39;S)

(the man saw the table)

>>> generate(&#39;S&#39;)

[&#39;the&#39;, &#39;man&#39;, &#39;saw&#39;, &#39;the&#39;, &#39;table&#39;]

>>> &#39; &#39;.join(generate(&#39;S&#39;))

&#39;the man saw the table&#39;

Python中的grammer比Lisp中的丑陋&#xff0c;这让我很担心&#xff0c;所以我考虑在Python中写一个解析器(后来发现已经有一些写好的&#xff0c;并且可以免费获得的)&#xff0c;以及重载一些内置的操作符。第二种方法在一些应用中是可行的&#xff0c;例如我写Expr class&#xff0c;这是用来表现和操纵逻辑表达式的。但是对于这个应用而言&#xff0c;一个简单、定制的语法规则解析器就够了&#xff1a;一个语法规则是一个用“|”分开的&#xff0c;可选部分的列表&#xff0c;每个可选部分都是由空格(" ")分隔的单词列表。把grammar程序重写为一个更加符合Python惯用法的程序&#xff0c;而不是Lisp程序的翻译&#xff0c;下面就是该程序&#xff1a;

Python Program simple.py (idiomatic version)

"""Generate random sentences from a grammar. The grammar

consists of entries that can be written as S &#61; &#39;NP VP | S and S&#39;,

which gets translated to {&#39;S&#39;: [[&#39;NP&#39;, &#39;VP&#39;], [&#39;S&#39;, &#39;and&#39;, &#39;S&#39;]]}, 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 &#39;S&#39;) as input."""

import random

def make_grammar(**grammar):

"Create a dictionary mapping symbols to alternatives."

for (cat, rhs) in grammar.items():

grammar[cat] &#61; [alt.split() for alt in rhs.split(&#39;|&#39;)]

return grammar

grammar &#61; make_grammar(

S &#61; &#39;NP VP&#39;,

NP &#61; &#39;Art N&#39;,

VP &#61; &#39;V NP&#39;,

Art &#61; &#39;the | a&#39;,

N &#61; &#39;man | ball | woman | table&#39;,

V &#61; &#39;hit | took | saw | liked&#39;

)

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&#61;&#39;S&#39;):

"""Replace symbol(s) in the space-delimited input string by a random entry

in grammar (recursively until terminals); join back into a string."""

return &#39; &#39;.join(rewrite(symbols.split()))

def generate_tree(symbols&#61;&#39;S&#39;):

"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())



推荐阅读
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • 不同优化算法的比较分析及实验验证
    本文介绍了神经网络优化中常用的优化方法,包括学习率调整和梯度估计修正,并通过实验验证了不同优化算法的效果。实验结果表明,Adam算法在综合考虑学习率调整和梯度估计修正方面表现较好。该研究对于优化神经网络的训练过程具有指导意义。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • OO第一单元自白:简单多项式导函数的设计与bug分析
    本文介绍了作者在学习OO的第一次作业中所遇到的问题及其解决方案。作者通过建立Multinomial和Monomial两个类来实现多项式和单项式,并通过append方法将单项式组合为多项式,并在此过程中合并同类项。作者还介绍了单项式和多项式的求导方法,并解释了如何利用正则表达式提取各个单项式并进行求导。同时,作者还对自己在输入合法性判断上的不足进行了bug分析,指出了自己在处理指数情况时出现的问题,并总结了被hack的原因。 ... [详细]
  • 本文介绍了在处理不规则数据时如何使用Python自动提取文本中的时间日期,包括使用dateutil.parser模块统一日期字符串格式和使用datefinder模块提取日期。同时,还介绍了一段使用正则表达式的代码,可以支持中文日期和一些特殊的时间识别,例如'2012年12月12日'、'3小时前'、'在2012/12/13哈哈'等。 ... [详细]
  • Python爬虫中使用正则表达式的方法和注意事项
    本文介绍了在Python爬虫中使用正则表达式的方法和注意事项。首先解释了爬虫的四个主要步骤,并强调了正则表达式在数据处理中的重要性。然后详细介绍了正则表达式的概念和用法,包括检索、替换和过滤文本的功能。同时提到了re模块是Python内置的用于处理正则表达式的模块,并给出了使用正则表达式时需要注意的特殊字符转义和原始字符串的用法。通过本文的学习,读者可以掌握在Python爬虫中使用正则表达式的技巧和方法。 ... [详细]
  • 本文介绍了Java集合库的使用方法,包括如何方便地重复使用集合以及下溯造型的应用。通过使用集合库,可以方便地取用各种集合,并将其插入到自己的程序中。为了使集合能够重复使用,Java提供了一种通用类型,即Object类型。通过添加指向集合的对象句柄,可以实现对集合的重复使用。然而,由于集合只能容纳Object类型,当向集合中添加对象句柄时,会丢失其身份或标识信息。为了恢复其本来面貌,可以使用下溯造型。本文还介绍了Java 1.2集合库的特点和优势。 ... [详细]
  • MySQL多表数据库操作方法及子查询详解
    本文详细介绍了MySQL数据库的多表操作方法,包括增删改和单表查询,同时还解释了子查询的概念和用法。文章通过示例和步骤说明了如何进行数据的插入、删除和更新操作,以及如何执行单表查询和使用聚合函数进行统计。对于需要对MySQL数据库进行操作的读者来说,本文是一个非常实用的参考资料。 ... [详细]
  • PHP图片截取方法及应用实例
    本文介绍了使用PHP动态切割JPEG图片的方法,并提供了应用实例,包括截取视频图、提取文章内容中的图片地址、裁切图片等问题。详细介绍了相关的PHP函数和参数的使用,以及图片切割的具体步骤。同时,还提供了一些注意事项和优化建议。通过本文的学习,读者可以掌握PHP图片截取的技巧,实现自己的需求。 ... [详细]
  • 3.223.28周学习总结中的贪心作业收获及困惑
    本文是对3.223.28周学习总结中的贪心作业进行总结,作者在解题过程中参考了他人的代码,但前提是要先理解题目并有解题思路。作者分享了自己在贪心作业中的收获,同时提到了一道让他困惑的题目,即input details部分引发的疑惑。 ... [详细]
  • 本文介绍了在Java中检查字符串是否仅包含数字的方法,包括使用正则表达式的示例代码,并提供了测试案例进行验证。同时还解释了Java中的字符转义序列的使用。 ... [详细]
  • Ihaveaworkfolderdirectory.我有一个工作文件夹目录。holderDir.glob(*)>holder[ProjectOne, ... [详细]
author-avatar
宝贝小左手很开心
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有