Decoratori Python e macro Lisp


18

Quando guardano i decoratori di Python qualcuno ha fatto la dichiarazione, che sono potenti come le macro di Lisp (in particolare Clojure).

Guardando gli esempi forniti in PEP 318, mi sembra che siano solo un modo fantasioso di usare semplici funzioni di ordine superiore in 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):
    ...

Non ho visto alcun codice trasformarsi in nessuno degli esempi, come descritto in Anatomy of a Clojure Macro . Inoltre, la mancata omogeneità di Python potrebbe rendere impossibile la trasformazione del codice.

Quindi, come si confrontano questi due e puoi dire che sono quasi uguali in ciò che puoi fare? Le prove sembrano puntare contro di essa.

Modifica: Sulla base di un commento, sto cercando due cose: il confronto su "potente come" e su "facile da fare cose fantastiche con".


12
Naturalmente i decoratori non sono macro reali. Non possono tradurre un linguaggio arbitrario (con una sintassi totalmente diversa) in Python. Coloro che sostengono il contrario semplicemente non capiscono le macro.
SK-logic,

1
Python non è omoiconico, tuttavia è molto molto dinamico. L'omoiconicità è necessaria solo per potenti trasformazioni di codice se si desidera farlo in fase di compilazione: se si dispone del supporto per l'accesso diretto all'AST compilato e agli strumenti per modificarlo, è possibile farlo in fase di esecuzione indipendentemente dalla sintassi del linguaggio. Detto questo, "tanto potente quanto" e "altrettanto facile fare cose fantastiche" sono concetti molto diversi.
Phoshi

Forse dovrei cambiare la domanda in "così facile fare cose fantastiche con" allora. ;)
Profpatsch il

Forse qualcuno può hackerare alcune funzioni comparabili di ordine superiore Clojure con l'esempio di Python sopra. Ho provato ma ho attraversato la mia mente nel processo. Poiché l'esempio di Python utilizza attributi di oggetto, questo deve essere leggermente diverso.
Profpatsch,

@Phoshi Alterare l'AST compilato in fase di esecuzione è noto come: codice automodificante .
Kaz,

Risposte:


16

Un decoratore è fondamentalmente solo una funzione .

Esempio in Common Lisp:

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

In sopra la funzione è un simbolo (che verrebbe restituito da DEFUN) e inseriamo gli attributi nell'elenco delle proprietà del simbolo .

Ora possiamo scrivere attorno ad una definizione di funzione:

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

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

)  

Se vogliamo aggiungere una sintassi elaborata come in Python, scriviamo una macro di lettura . Una macro lettore ci consente di programmare a livello di sintassi di s-expression:

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

Quindi possiamo scrivere:

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

Il lettore Lisp legge sopra per:

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

Ora abbiamo una forma di decoratori in Common Lisp.

Combinazione di macro e macro del lettore.

In realtà farei sopra la traduzione in codice reale usando una macro, non una funzione.

(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))))

L'uso è come sopra con la stessa macro del lettore. Il vantaggio è che il compilatore Lisp vede ancora come un cosiddetto modulo di livello superiore - le forme * compilatore di file tratta di alto livello appositamente, ad esempio aggiunge informazioni su di loro in fase di compilazione ambiente . Nell'esempio sopra possiamo vedere che la macro esamina il codice sorgente ed estrae il nome.

Il lettore Lisp legge l'esempio sopra in:

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

Che poi si espande in macro:

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

Le macro sono molto diverse dalle macro dei lettori .

Le macro ottengono il codice sorgente passato, possono fare quello che vogliono e quindi restituire il codice sorgente. La sorgente di input non deve essere un codice Lisp valido. Può essere qualsiasi cosa e potrebbe essere scritto totalmente diverso. Il risultato deve quindi essere un codice Lisp valido. Ma se anche il codice generato utilizza una macro, la sintassi del codice incorporato nella chiamata macro potrebbe essere di nuovo una sintassi diversa. Un semplice esempio: si potrebbe scrivere una macro matematica che accetterebbe una sorta di sintassi matematica:

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

L'espressione y = 3 x ^ 2 - 4 x + 3non è un codice Lisp valido, ma la macro potrebbe ad esempio analizzarlo e restituire un codice Lisp valido in questo modo:

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

Ci sono molti altri casi d'uso di macro in Lisp.


8

In Python (il linguaggio) i decoratori non possono modificare la funzione, ma solo avvolgerla, quindi sono decisamente molto meno potenti delle macro lisp.

In CPython (l'interprete) i decoratori possono modificare la funzione perché hanno accesso al bytecode, ma la funzione viene compilata per prima e può essere manipolata dal decoratore, quindi non è possibile modificare la sintassi, una cosa lisp-macro -equivalente avrebbe bisogno di fare.

Nota che i lisps moderni non usano le espressioni S come bytecode, quindi le macro che lavorano sugli elenchi di espressioni S funzionano sicuramente prima della compilazione bytecode come notato sopra, in Python il decoratore lo segue.


1
Non è necessario modificare la funzione. Hai solo bisogno di leggere il codice della funzione in qualche forma (in pratica, questo significa bytecode). Non che questo lo renda più pratico.

2
@delnan: tecnicamente, anche lisp non lo sta modificando; lo sta usando come sorgente per generarne uno nuovo e così sarebbe Python, sì. Il problema risiede nell'assenza di token list o AST e nel fatto che il compilatore si è già lamentato di alcune cose che altrimenti potresti consentire nella macro.
Jan Hudec,

4

È abbastanza difficile usare i decoratori Python per introdurre nuovi meccanismi di flusso di controllo.

È quasi banale usare le macro Common Lisp per introdurre nuovi meccanismi di flusso di controllo.

Da ciò, probabilmente ne consegue che non sono ugualmente espressivi (scelgo di interpretare "potente" come "espressivo", poiché penso che ciò che realmente significano).


Oserei dires/quite hard/impossible/

@delnan Beh, non sarebbe andato tutto così lontano come a dire "impossibile", ma avresti sicuramente a lavorarci sopra.
Vatine,

0

È sicuramente correlata alla funzionalità, ma da un decoratore Python non è banale modificare il metodo chiamato (che sarebbe il fparametro nel tuo esempio). Per modificarlo si potrebbe impazzire con l' AST modulo), ma si sarebbe in qualche programmazione piuttosto complicato.

Le cose su questa linea sono state fatte comunque: dai un'occhiata a pacchetto macropy per alcuni esempi davvero strabilianti .


3
Anche le astcose che trasformano in Python non sono uguali alle macro di Lisp. Con Python, il linguaggio di origine dovrebbe essere Python, con le macro Lisp il linguaggio di origine trasformato da una macro può essere, letteralmente, qualsiasi cosa. Pertanto, la metaprogrammazione Python è adatta solo per cose semplici (come AoP), mentre la metaprogrammazione Lisp è utile per l'implementazione di potenti compilatori eDSL.
SK-logic,

1
Il fatto è che la macropia non è implementata usando i decoratori. Utilizza la sintassi del decoratore (perché deve usare una sintassi python valida), ma è implementata dirottando il processo di compilazione dei byte da un hook di importazione.
Jan Hudec,

@ SK-logic: in Lisp anche la lingua di origine deve essere lisp. La sintassi di Lisp è molto semplice ma flessibile, mentre la sintassi di Python è molto più complessa e non così flessibile.
Jan Hudec,

1
@JanHudec, in linguaggio sorgente Lisp può avere qualsiasi sintassi (intendo, qualsiasi ) - vedi macro lettore.
SK-logic,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.