Python装饰器和Lisp宏


18

在查看Python装饰器时,有人声明,它们与Lisp宏(尤其是Clojure)一样强大。

看一下PEP 318中给出的示例,在我看来,它们只是在Lisp中使用普通的旧高阶函数的一种奇特方式:

def attrs(**kwds):
    def decorate(f):
        for k in kwds:
            setattr(f, k, kwds[k])
        return f
    return decorate

@attrs(versionadded="2.2",
       author="Guido van Rossum")
def mymethod(f):
    ...

在任何示例中都没有看到任何代码转换,如Clojure Macro的解剖中所述。另外,Python缺少同源性 可能使代码转换变得不可能。

那么,这两个如何比较,您能说他们在做事上大致相等吗?证据似乎与此相反。

编辑:根据评论,我正在寻找两件事:比较“功能强大”和“易于处理”。


12
当然,装饰器不是真正的宏。他们无法将任意语言(语法完全不同)翻译成python。那些主张相反的人根本不理解宏。
SK-logic

1
Python不是谐音,但是它非常动态。仅当要在编译时进行功能强大的代码转换时才需要同声性-如果您支持直接访问已编译的AST和支持对其进行更改的工具,则无论语言语法如何,都可以在运行时进行。话虽这么说,“功能强大”和“操作起来就很容易”是非常不同的概念。
Phoshi

也许我应该将问题更改为“做一件很棒的事情一样容易”。;)
Profpatsch

也许有人可以破解一些与上述Python示例类似的Clojure高阶函数。我尝试了一下,但在过程中却纵横交错。由于Python示例使用对象属性,因此这必须有所不同。
Profpatsch

@Phoshi在运行时更改已编译的AST被称为:自修改代码
卡兹(Kaz)

Answers:


16

一个装饰基本上是一个功能

Common Lisp中的示例:

(defun attributes (keywords function)
  (loop for (key value) in keywords
        do (setf (get function key) value))
  function)

在上面的函数中是一个符号(它将由返回DEFUN),我们将属性放在符号的属性列表中

现在我们可以围绕函数定义编写它:

(attributes
  '((version-added "2.2")
    (author "Rainer Joswig"))

  (defun foo (a b)
    (+ a b))

)  

如果要像在Python中那样添加精美的语法,请编写一个reader宏。Reader宏允许我们在s-expression语法级别进行编程:

(set-macro-character
 #\@
 (lambda (stream char)
   (let ((decorator (read stream))
         (arg       (read stream))
         (form      (read stream)))
     `(,decorator ,arg ,form))))

然后我们可以写:

@attributes'((version-added "2.2")
             (author "Rainer Joswig"))
(defun foo (a b)
  (+ a b))

Lisp 阅读器的阅读内容如下:

(ATTRIBUTES (QUOTE ((VERSION-ADDED "2.2")
                    (AUTHOR "Rainer Joswig")))
            (DEFUN FOO (A B) (+ A B)))

现在,我们在Common Lisp中有了一种装饰器形式。

结合宏和阅读器宏。

实际上,我会使用宏而不是函数在实际代码中进行上述翻译。

(defmacro defdecorator (decorator arg form)
  `(progn
     ,form
     (,decorator ,arg ',(second form))))

(set-macro-character
 #\@
 (lambda (stream char)
   (declare (ignore char))
   (let* ((decorator (read stream))
          (arg       (read stream))
          (form      (read stream)))
     `(defdecorator ,decorator ,arg ,form))))

上面的用法与相同的阅读器宏相同。优点是Lisp编译器仍然将其视为所谓的顶层表单 -* file编译器专门处理顶层表单,例如,将有关它们的信息添加到编译时环境中。在上面的示例中,我们可以看到宏会查看源代码并提取名称。

Lisp 阅读器将上面的示例读取为:

(DEFDECORATOR ATTRIBUTES
  (QUOTE ((VERSION-ADDED "2.2")
           (AUTHOR "Rainer Joswig")))
  (DEFUN FOO (A B) (+ A B)))

然后将宏扩展为:

(PROGN (DEFUN FOO (A B) (+ A B))
       (ATTRIBUTES (QUOTE ((VERSION-ADDED "2.2")
                           (AUTHOR "Rainer Joswig")))
                   (QUOTE FOO)))

宏与阅读器宏有很大不同。

宏获取传递的源代码,可以执行任何所需的操作,然后返回源代码。输入源不需要是有效的Lisp代码。它可以是任何东西,也可以写成完全不同的东西。然后,结果必须是有效的Lisp代码。但是,如果生成的代码也使用宏,则嵌入在宏调用中的代码的语法可能又是另一种语法。一个简单的例子:可以编写一个数学宏,该宏可以接受某种数学语法:

(math y = 3 x ^ 2 - 4 x + 3)

该表达式y = 3 x ^ 2 - 4 x + 3不是有效的Lisp代码,但是宏可以例如解析它并返回有效的Lisp代码,如下所示:

(setq y (+ (* 3 (expt x 2))
           (- (* 4 x))
           3))

Lisp中还有许多其他宏的用例。


8

在Python(语言)中,装饰器无法修改函数,只能对其进行包装,因此它们的功能肯定不如Lisp宏强大。

在CPython(解释器)中,修饰器可以修改函数,因为它们可以访问字节码,但是该函数首先被编译,然后可以由修饰器弄弄,因此无法更改语法,这就是lisp-macro -等效将需要做。

请注意,现代lisps并不将S表达式用作字节码,因此,如上所述,在S表达式列表上工作的宏肯定在字节码编译之前就可以工作,在python中,装饰器在其后运行。


1
您不需要修改功能。您只需要阅读某种形式的函数代码(实际上,这就是字节码)。但这并不意味着它更加实用。

2
@delnan:从技术上讲,lisp也不修改它;它使用它作为源来生成一个新的,python也是如此。问题在于缺少令牌列表或AST,并且编译器已经抱怨您可能在宏中允许的某些事情。
2013年

4

使用Python装饰器引入新的控制流机制非常困难。

使用Common Lisp宏引入新的控制流机制很简单。

由此看来,它们可能不具有同等的表现力(我选择将“功能强大”解释为“表现力”,因为我认为它们的实际含义)。


我敢说s/quite hard/impossible/

@delnan好吧,我不会走竟然说“不可能”,但你绝对不得不工作吧。
Vatine

0

这当然是相关的功能,但是对于Python装饰器而言,修改被调用的方法并非易事(f在您的示例中为参数)。要对其进行修改,您可能会对ast模块感到疯狂),但是您可能需要进行一些非常复杂的编程。

不过,这方面的工作已经完成:请查看macropy软件包中的一些令人费解的示例。


3
甚至astpython中的-transforming东西也不等于Lisp宏。对于Python,源语言应该是Python,对于Lisp宏,由宏转换的源语言实际上可以是任何东西。因此,Python元编程仅适用于简单的事物(例如AoP),而Lisp元编程对于实现功能强大的eDSL编译器很有用。
SK-logic

1
事实是,宏不是使用装饰器实现的。它使用装饰器语法(因为必须使用有效的python语法),但是它是通过从导入钩子劫持字节编译过程来实现的。
2013年

@ SK-logic:在Lisp中,源语言也必须是Lisp。只是Lisp语法非常简单但是很灵活,而python语法要复杂得多而不是那么灵活。
Jan Hudec

1
@JanHudec,在Lisp源语言中可以具有任何(我的意思是任何)语法-请参阅阅读器宏。
SK-logic
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.