21

如果 Python 有一个类似于 Lisp/Scheme 的宏工具(类似于MetaPython),你会如何使用它?

如果你是一名 Lisp/Scheme 程序员,你会使用宏来处理哪些事情(除了在 Python 中具有明确的句法并行性的事情,例如 while 循环)?

4

16 回答 16

15

我相信宏与 Python 的文化背道而驰。Lisp 中的宏允许使用大泥球方法;您可以重新定义语言以更适合您的问题领域。相反,Pythonic 代码使用 Python 最自然的内置特性来解决问题,而不是用另一种语言更自然的方式来解决问题。

宏本质上是 unpythonic 的。

于 2009-04-18T23:05:46.877 回答
15

这是一个有点晚的答案,但MacroPy是我的一个新项目,用于将宏引入 Python。我们有一个相当丰富的演示列表,所有这些都是需要宏来实现的用例,例如提供了一种非常简洁的声明类的方式:

@case
class Point(x, y)

p = Point(1, 2)
print p.x   # 1
print p     # Point(1, 2)

MacroPy 已被用于实现以下功能:

  • 案例类,来自 Scala的简单代数数据类型
  • 来自函数式编程世界的模式匹配
  • 尾调用优化
  • Quasiquotes,一种操作程序片段的快速方法
  • 字符串插值,许多语言中的一个共同特性,以及 Pyxl。
  • 跟踪和智能断言
  • PINQ to SQLAlchemy,从 C# 克隆 LINQ to SQL
  • 来自 Scala 和 Groovy 的快速 Lambda,
  • 解析器组合器,灵感来自Scala 的.

查看链接页面以了解更多信息;我想我可以自信地说,我们展示的用例远远超过了迄今为止任何人在此线程上提出的任何建议 =D

于 2013-05-11T04:19:22.793 回答
11

lisp 宏的一些示例:

  • ITERATE这是一个有趣且可扩展的循环工具
  • CL-YACC / FUCC是在编译时生成解析器的解析器生成器
  • CL-WHO,它允许指定带有静态和动态部分的 html 文档
  • Parenscript是一个 javascript 代码生成器
  • 各种简单的代码包装器,例如错误处理程序(我有一个 with-gtk-error-message-handler 执行代码并在发生未处理的错误时显示 GtkMessageDialog),执行程序(例如,给定代码,在不同的线程中执行它;我有一个在不同线程中执行代码的主线程内宏;PCall库使用宏来包装要并发执行的代码)
  • 具有宏的 GUI 构建器(例如,指定小部件层次结构和小部件的属性,并使用宏生成代码以创建所有小部件)
  • 在编译期间使用外部资源的代码生成器。例如,处理 C 头文件并生成 FFI 代码的宏或基于数据库模式生成类定义的宏
  • 声明性 FFI。例如,指定外部结构、函数、它们的参数类型并使用宏来生成相应的 lisp 结构、具有类型映射和编组代码的函数
  • Common Lisp 的基于延续的 Web 框架使用将代码转换为 CPS(延续传递样式)形式的宏。
于 2009-04-19T05:45:58.990 回答
5

有一个邮件列表发布archive.org mirror)很好地解释了这一点。这篇文章是关于 Perl 的,但它也适用于 Python。

于 2009-04-18T23:01:42.923 回答
4

在 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

于 2009-04-18T23:22:39.177 回答
4

这是我遇到的一个真实示例,它对于宏或真正的元编程支持来说是微不足道的,但由于 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

于 2010-03-14T22:28:58.167 回答
3

我之前见过的一些用例包括创建类工厂或从生产代码中剥离日志语句。

于 2009-04-18T23:00:39.307 回答
2

我认为 Python 不需要宏,因为它们对两件事很有用:

  1. 为某事创建 DSL 或更雄辩的语法(Lisp LOOP 宏就是一个很好的例子)。在这种情况下,Python 哲学故意决定反对它。如果您缺少一些明确的符号,您可以随时要求 PEP。

  2. 通过在编译时预先计算事物来使事情变得更快。Python 不以速度为导向,因此您始终可以使用函数。

我并不是说宏是错误的,只是它们不符合 Python 哲学。你总是可以不用它们而没有太多的代码重复,因为你有鸭子类型和运算符重载。

作为旁注,我更愿意看到 Lisp 在 Python 中的重新启动,而不是宏。

于 2009-04-19T07:05:08.690 回答
1

阅读“The Lambda Papers”,您可能会大致了解为什么要使用宏。

你应该从“AIM-353 Lambda:The Ultimate Imperative”开始,然后是“AIM-443 Lambda: The Ultimate GOTO”。两者都可以在这里找到:

http://library.readscheme.org/page1.html

于 2009-06-11T19:37:34.280 回答
1

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 行清晰高效的 p​​ython 代码。

如果 Python 可以在未来的版本中包含宏,它会变得更加令人印象深刻。

于 2011-06-01T10:57:08.530 回答
1

我会用它来包装yield以使我能够构建更强大的生成器管道。

于 2013-03-25T08:55:04.390 回答
1

目前,将特性添加到 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()

这些将很容易实现为宏。更复杂、更有用的宏包括:

  • Ruby 风格的字符串插值,例如"hello there {user}"(可能最好作为阅读器宏实现)
  • 模式匹配

目前,这些只是其他语言的功能。使用宏,我可以将它们添加到 Python。我什至可以编写包含示例实现的 PEP。(一些 PEP 已经这样做了,但他们被迫修改解释器本身的 C 源代码。)

于 2013-04-23T16:27:33.173 回答
0

可能如果您想要在运行时使用源代码,例如用于调试(例如 printf 使用它的名称调试表达式的值,因此您不必编写两次)。

我能想到在 python 中做到这一点的唯一方法是将字符串传递给 eval。

于 2012-01-15T11:37:54.317 回答
0

好吧,我想代替

print >> sys.stderr, "abc"

来写

err "abc"

在一些有许多调试打印输出语句的脚本中。

我可以

import sys
err = sys.stderr

进而

print >> err, "abc"

哪个更短,但这仍然需要太多字符。

于 2012-04-10T05:51:02.123 回答
0

我想使用宏在 python 代码中启用 sql 语句。- 从表 1 中选择 *

于 2013-01-24T18:08:35.137 回答
0

来自 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(),))
于 2014-03-11T18:53:32.070 回答