“有人告诉我Python在根本上与Lisp相似,我浏览了Python文档后得出的结论是并非如此。当你开始使用Lisp时,它会执行“读取”,“求值”和“输出”,而这三个功能在Python中都是缺失的。”
Lisp的REPL和Python有根本技术差异吗?您可以举例说明Lisp REPL所能轻松实现的事情,在Python中难以做到的是什么?
支持Stallman的观点,Python在以下几个方面与典型的Lisp系统不同:
Lisp中的read
函数读取一个S表达式,它表示任意的数据结构,可以被视为数据,也可以作为代码进行评估。Python中最接近的东西读取单个字符串,如果您想让它有意义,就必须自己解析。
Lisp中的eval
函数可执行任何Lisp代码。Python中的eval
函数仅评估表达式,并需要exec
语句运行语句。但是这两个函数都使用文本表示的Python源代码工作,如果要“eval” Python AST,则必须跨越一堆障碍。
Lisp中的print
函数以与read
接受的完全相同的形式编写S表达式。print
在Python中打印出由您正在尝试打印的数据定义的内容,这肯定不总是可逆的。
Stallman的声明有些虚伪,因为显然Python确实有名为eval
和print
的函数,但它们所做的事情与他期望的不同(并且较差)。
在我看来,Python确实具有与Lisp相似的一些方面,我可以理解为什么人们可能会建议Stallman尝试Python。然而,正如Paul Graham在《Lisp有何不同》中所述,任何包括Lisp所有功能的编程语言,必须是 Lisp。
Stallman认为,与Lisp相比,Python的REPL没有实现显式的“reader”,导致其看起来不够完整。Reader是将文本输入流转换为内存的组件,就像语言中集成的XML解析器一样,用于源代码和数据。这不仅有助于编写宏(在理论上,Python可以使用ast
模块实现),还有助于调试和内省。
假设你对如何实现incf
特殊形式感兴趣,可以这样测试:
[4]> (macroexpand '(incf a))
(SETQ A (+ A 1)) ;
然而, incf
的作用不仅仅是增加符号值。当被要求增加哈希表条目时,它到底做了什么?让我们看一下:
[2]> (macroexpand '(incf (gethash htable key)))
(LET* ((#:G3069 HTABLE) (#:G3070 KEY) (#:G3071 (+ (GETHASH #:G3069 #:G3070) 1)))
(SYSTEM::PUTHASH #:G3069 #:G3070 #:G3071)) ;
在这里,我们了解到incf
调用一个特定于系统的puthash
函数,这是这个Common Lisp系统的实现细节。请注意,“打印机”如何利用“阅读器”已知的功能,例如使用#:
语法引入匿名符号,并在扩展表达式的范围内引用相同的符号。在Python中模拟这种检查会更冗长,而且不太容易访问。
除了在REPL上的明显用途外,有经验的Lisper还使用print
和read
作为一种简单且易于使用的序列化工具,类似于XML或JSON。虽然Python有等效于Lisp的print
的str
函数,但它缺乏read
的等效函数,最接近的等效函数是eval
。当然,eval
混淆了解析和评估这两个不同的概念,这导致了像这样的问题和这样的解决方案,并且是Python论坛上反复出现的主题。这在Lisp中不会成为问题,因为阅读器和评估器是清晰分离的。
最后,读取器工具的高级功能使程序员能够以甚至宏无法实现的方式扩展语言。一个典型的例子是Mark Kantrowitz的infix
包,实现了完整功能的中缀语法作为读取宏。
Python 2.7.2 (default, Jun 20 2012, 16:23:33)
[GCC 4.2.1 Compatible Apple Clang 4.0 (tags/Apple/clang-418.0.60)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> a+2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>>
您遇到了错误信息,仅此而已。
与 CLISP REPL 相比较:
rjmba:~ joswig$ clisp
i i i i i i i ooooo o ooooooo ooooo ooooo
I I I I I I I 8 8 8 8 8 o 8 8
I \ `+' / I 8 8 8 8 8 8
\ `-+-' / 8 8 8 ooooo 8oooo
`-__|__-' 8 8 8 8 8
| 8 o 8 8 o 8 8
------+------ ooooo 8oooooo ooo8ooo ooooo 8
Welcome to GNU CLISP 2.49 (2010-07-07) <http://clisp.cons.org/>
Copyright (c) Bruno Haible, Michael Stoll 1992, 1993
Copyright (c) Bruno Haible, Marcus Daniels 1994-1997
Copyright (c) Bruno Haible, Pierpaolo Bernardi, Sam Steingold 1998
Copyright (c) Bruno Haible, Sam Steingold 1999-2000
Copyright (c) Sam Steingold, Bruno Haible 2001-2010
Type :h and hit Enter for context help.
[1]> (+ a 2)
*** - SYSTEM::READ-EVAL-PRINT: variable A has no value
The following restarts are available:
USE-VALUE :R1 Input a value to be used instead of A.
STORE-VALUE :R2 Input a new value for A.
ABORT :R3 Abort main loop
Break 1 [2]>
Break 1 [2]> :r1
Use instead of A> 2
4
[3]>
Python的交互模式与从文件中读取代码的模式在几个关键方面有所不同,这可能与语言的文本表示方式有关。Python也不是同构的,这使我称其为“交互模式”而不是“读取-评估-打印循环”。除此之外,我认为这更多是程度上的差异,而不是本质上的差异。
现在,在Python代码文件中,您可以轻松插入空白行,这实际上接近于“本质上的差异”:
def foo(n):
m = n + 1
return m
*
,在Python中为 _
),但CL还具有 **
(上一个表达式的值)和 ***
(之前那个的值)以及 +
, ++
和 +++
(表达式本身)。 CL还不区分表达式和语句(实质上,一切都是表达式),所有这些都有助于构建更丰富的REPL体验。
print()
使用repr()
而不是str()
。也就是说,print(repr(eval(raw_input("> "))))
很接近 REPL。 - Frédéric Hamidieval
的是简单地运行该对象:name()
,如果name
是指函数。只有print
确实具有不同的属性:打印 Python 表达式或函数通常不能给我们再次解析相同的东西。 - alexis