如果 Python 有一个类似于 Lisp/Scheme 的宏工具(类似于MetaPython),你会如何使用它?
如果你是一名 Lisp/Scheme 程序员,你会使用宏来处理哪些事情(除了在 Python 中具有明确的句法并行性的事情,例如 while 循环)?
如果 Python 有一个类似于 Lisp/Scheme 的宏工具(类似于MetaPython),你会如何使用它?
如果你是一名 Lisp/Scheme 程序员,你会使用宏来处理哪些事情(除了在 Python 中具有明确的句法并行性的事情,例如 while 循环)?
我相信宏与 Python 的文化背道而驰。Lisp 中的宏允许使用大泥球方法;您可以重新定义语言以更适合您的问题领域。相反,Pythonic 代码使用 Python 最自然的内置特性来解决问题,而不是用另一种语言更自然的方式来解决问题。
宏本质上是 unpythonic 的。
这是一个有点晚的答案,但MacroPy是我的一个新项目,用于将宏引入 Python。我们有一个相当丰富的演示列表,所有这些都是需要宏来实现的用例,例如提供了一种非常简洁的声明类的方式:
@case
class Point(x, y)
p = Point(1, 2)
print p.x # 1
print p # Point(1, 2)
MacroPy 已被用于实现以下功能:
查看链接页面以了解更多信息;我想我可以自信地说,我们展示的用例远远超过了迄今为止任何人在此线程上提出的任何建议 =D
lisp 宏的一些示例:
有一个邮件列表发布(archive.org mirror)很好地解释了这一点。这篇文章是关于 Perl 的,但它也适用于 Python。
在 lisp 中,宏只是抽象概念的另一种方式。
这是一个用 clojure 编写的不完整光线追踪器的示例:
(defmacro per-pixel
"Macro.
Excecutes body for every pixel. Binds i and j to the current pixel coord."
[i j & body]
`(dotimes [~i @width]
(dotimes [~j @height]
~@body)))
如果你想对坐标为 (i,j) 的每个像素做一些事情,比如说,如果 i 是偶数,则画一个黑色像素,你会写:
(per-pixel i,j
(if (even? i)
(draw-black i,j)))
没有宏就不可能做到这一点,因为@body 可以表示内部的任何内容(每像素 ij @body)
在 python 中也有可能发生这样的事情。你需要使用装饰器。你不能用 lisp 宏做所有能做的事情,但它们非常强大
查看这个装饰器教程: http ://www.artima.com/weblogs/viewpost.jsp?thread=240808
这是我遇到的一个真实示例,它对于宏或真正的元编程支持来说是微不足道的,但由于 Python 中两者都没有,因此必须使用 CPython 字节码操作来完成:
http://www.aminus.net/dejavu/chrome/common/doc/2.0a/html/intro.html#cpython
这就是在 Common Lisp 中使用常规宏和 read-macros 来扩展语法的组合解决问题的方式(如果没有后者,它可以完成,但不是前者):
http://clsql.b9.com/manual/csql-find.html
在 Smalltalk 中使用闭包和元编程解决了同样的问题(Smalltalk 是为数不多的真正获得正确消息传递的单调度 OO 语言之一):
http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg02096.html
在这里,我尝试在 Common Lisp 中实现 Smalltalk 方法,这很好地说明了后者如何支持元编程:
http://carcaddar.blogspot.com/2009/04/closure-orientated-metaprogramming-via.html
我认为 Python 不需要宏,因为它们对两件事很有用:
为某事创建 DSL 或更雄辩的语法(Lisp LOOP 宏就是一个很好的例子)。在这种情况下,Python 哲学故意决定反对它。如果您缺少一些明确的符号,您可以随时要求 PEP。
通过在编译时预先计算事物来使事情变得更快。Python 不以速度为导向,因此您始终可以使用函数。
我并不是说宏是错误的,只是它们不符合 Python 哲学。你总是可以不用它们而没有太多的代码重复,因为你有鸭子类型和运算符重载。
作为旁注,我更愿意看到 Lisp 在 Python 中的重新启动,而不是宏。
阅读“The Lambda Papers”,您可能会大致了解为什么要使用宏。
你应该从“AIM-353 Lambda:The Ultimate Imperative”开始,然后是“AIM-443 Lambda: The Ultimate GOTO”。两者都可以在这里找到:
Hy,为了我自己的使用,我创建了一个 Python 模块 (Espy),它允许使用参数、循环和条件代码生成宏定义:您创建一个 source.espy 文件,然后启动相应的函数,然后生成 source.py。
它允许如下语法:
macro repeat(arg1):
for i in range(%arg1%):
socket
print "stop"
...
repeat(5):
print "Hi everybody"
print "See you soon"
相当于:
...
for i in range(5):
print "Hi everybody"
print "See you soon"
print "stop"
其他语法:
macro doit(arg1):
for i in %arg1%:
socket suit(arg2):
socket
print %arg2%
socket check(arg3):
if %arg2%==%arg3%:
socket
...
#use
doit(range(10)):
suit(result):
result=i*i
check(result,25):
print "I knew that 5*5 == 25"
相当于:
for i in range(10):
result=i*i
print result
if result==25:
print "I knew that 5*5 == 25"
此外,Espy 有 2 个函数:“macro for”和“macro if”。一个例子:
macro for v in [6,10,12,20,23]:
macro if 7<%v%<22:
True:
print "At %v%, I'm awake."
False:
print "At %v%, I'm sleeping."
由 Espy 翻译为:
print "At 6, I'm sleeping."
print "At 10, I'm awake."
print "At 12, I'm awake."
print "At 20, I'm awake."
print "At 23, I'm sleeping."
完整的文档和免费下载可以在这里找到:http ://elp.chronocv.fr
我在很多情况下都使用这个模块。它允许更结构化和更短的代码。有了它,我从 1000 行 espy 代码中为一个新的国际象棋引擎项目(仍在进行中)生成了 65000 行清晰高效的 python 代码。
如果 Python 可以在未来的版本中包含宏,它会变得更加令人印象深刻。
我会用它来包装yield
以使我能够构建更强大的生成器管道。
目前,将特性添加到 Python 语言的唯一方法是通过 PEP(Python 增强提案)。这可能很慢,并且在您想向语言中添加仅对您的用例有用的功能时无济于事。
例如,有一个 PEP 可以添加一个 do-while 循环。这可能会被添加到 Python 中,但 PEP 是在 2003 年创建的。我今天想编写do-while
循环,如果 Python 有宏,我可以这样做。
同样,有一个PEP 可以添加标记的中断并继续,但被拒绝了。如果带标签的 break 语句能让我的代码更清晰,我可以用宏来实现它们。
除了 PEP,我还想要一个unless
宏。而不是写:
if not is_superman():
dodge_bullet()
我可以写:
unless is_superman():
dodge_bullet()
我想要一个case
宏(通常cond
在 Lisp 中调用)。而不是写:
if x == FOO:
do_stuff_with_foos()
elif x == BAR:
do_stuff_with_bars()
elif x == BAZ:
do_stuff_with_bazs()
我可以写:
switch x:
case FOO:
do_stuff_with_foos()
case BAR:
do_stuff_with_bars()
case BAZ:
do_stuff_with_bazs()
这些将很容易实现为宏。更复杂、更有用的宏包括:
"hello there {user}"
(可能最好作为阅读器宏实现)目前,这些只是其他语言的功能。使用宏,我可以将它们添加到 Python。我什至可以编写包含示例实现的 PEP。(一些 PEP 已经这样做了,但他们被迫修改解释器本身的 C 源代码。)
可能如果您想要在运行时使用源代码,例如用于调试(例如 printf 使用它的名称调试表达式的值,因此您不必编写两次)。
好吧,我想代替
print >> sys.stderr, "abc"
来写
err "abc"
在一些有许多调试打印输出语句的脚本中。
我可以
import sys
err = sys.stderr
进而
print >> err, "abc"
哪个更短,但这仍然需要太多字符。
我想使用宏在 python 代码中启用 sql 语句。- 从表 1 中选择 *
来自 C 世界,我非常感谢能够有效地记录丰富的消息。而不是写
if logger.level > logger.DEBUG:
logger.log('%s' % (an_expensive_call_that_gives_useful_info(),))
使用宏,可以改为执行类似的操作
DEBUG('%s' % (an_expensive_call_that_gives_useful_info(),))