Декоратори 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" . Крім того, відсутність гомоконічності Python може унеможливити перетворення коду.

Отже, як порівняти ці двоє і чи можете ви сказати, що вони приблизно рівні, що ви можете зробити? Докази, схоже, вказують на це.

Редагувати: На підставі коментаря я шукаю дві речі: порівняння на "настільки ж потужне, як" і на "як легко зробити дивовижні речі".


12
Звичайно, декоратори - не справжні макроси. Вони не можуть перекласти довільну мову (із зовсім іншим синтаксисом) у пітон. Ті, хто стверджує протилежне, просто не розуміють макросів.
SK-логіка

1
Python не гомоніконічний, проте дуже динамічний. Гомоїконічність потрібна лише для потужних перетворень коду, якщо ви хочете зробити це під час компіляції - якщо у вас є підтримка прямого доступу до складеного AST та інструменти для його зміни, ви можете це робити під час виконання незалежно від синтаксису мови. Як сказано, "настільки потужні, як" і "як легко зробити дивовижні речі" є дуже різними поняттями.
Фоші

Можливо, тоді я повинен змінити питання на "як легко робити дивовижні речі". ;)
Profpatsch

Можливо, хтось може зламати якусь порівнянну функцію Clojure вищого порядку з прикладом Python, наведеним вище. Я намагався, але пересікав розум у процесі. Оскільки в прикладі Python використовуються об'єктні атрибути, це має бути дещо іншим.
Profpatsch

@Phoshi Зміна складеного AST під час виконання роботи відома як: код, що самозмінюється .
Каз

Відповіді:


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, ми пишемо макрос читача . Макрос читача дозволяє нам програмувати на рівень синтаксису s-виразу:

(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 все ще сприймає його як так звану форму верхнього рівня - компілятор файлів * обробляє форми вищого рівня спеціально, наприклад, він додає інформацію про них у середовище часу компіляції. . У наведеному вище прикладі ми бачимо, що макрос заглядає у вихідний код і витягує ім'я.

Читач 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))

У Ліспі багато інших випадків використання макросів.


8

У програмі Python (мова) декоратори не можуть змінювати функцію, лише обертати її, тому вони, безумовно, набагато менш потужні, ніж макроси lisp.

У CPython (інтерпретаторі) декоратори можуть змінювати функцію, оскільки вони мають доступ до байтового коду, але функція складена спочатку і, ніж може прикрашати декоратор, тому змінити синтаксис неможливо, річ lisp-macro -еквівалент потрібно було б зробити.

Зауважте, що сучасні lisps не використовують S-вирази як байт-код, тому макроси, що працюють у списках S-виразів, безумовно, працюють перед компіляцією байт-кодів, як зазначено вище, в python декоратор працює після нього.


1
Вам не потрібно змінювати функцію. Потрібно лише прочитати код функції в якійсь формі (на практиці це означає байт-код). Не те, що це робить його більш практичним.

2
@delnan: Технічно lisp також не змінює його; він використовує його як джерело для створення нового, і так би пітон, так. Проблема полягає у відсутності списку токенів або AST і у тому, що компілятор вже скаржився на деякі речі, які ви інакше можете дозволити в макросі.
Ян Худек

4

Використовувати декоратори Python досить важко для впровадження нових механізмів управління потоком.

Мережу на тривіальне використання макросів Common Lisp для введення нових механізмів управління потоком.

З цього, мабуть, випливає, що вони не однаково виразні (я вирішую трактувати "потужних" як "виразних", оскільки я думаю, що те, що вони насправді означають).


Смію сказатиs/quite hard/impossible/

@delnan Ну, я не пішов би зовсім так далеко, щоб сказати «неможливо», але ви напевне повинні працювати на нього.
Ватін

0

Це, безумовно, пов'язано з функціональністю, але від декоратора Python не тривіально змінювати названий метод (це був би fпараметр у вашому прикладі). Щоб змінити його, ви можете зійти з розуму за допомогою модуля ast ), але ви будете готові до досить складного програмування.

Речі в цій лінії були зроблені, хоча: ознайомтеся з пакетом макрокопій, щоб ознайомитись із справді прикладними зразками.


3
Навіть ast-трансформуючий матеріал у python не дорівнює макросам Lisp. З Python мовою джерела має бути Python, а макроси Lisp вихідною мовою, перетвореною макросом, може бути буквально будь-що. Тому метапрограмування Python підходить лише для простих речей (наприклад, AoP), тоді як метапрограмування Lisp корисно для реалізації потужних компіляторів eDSL.
SK-логіка

1
Вся справа в тому, що макропія не реалізована за допомогою декораторів. Він використовує синтаксис декоратора (тому що він повинен використовувати дійсний синтаксис python), але він реалізується шляхом викрадення процесу збирання байтів із імпортного гачка.
Ян Худек

@ SK-логіка: В Lisp мова оригіналу також повинна бути lisp. Синтаксис просто Lisp дуже простий, але гнучкий, тоді як синтаксис python набагато складніший і не такий гнучкий.
Ян Худек

1
@JanHudec, мова мови Lisp може мати будь-який (я справді маю на увазі будь-який ) синтаксис - див. Макроси читачів.
SK-логіка
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.