Декоратори 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.