Python decorators และ Lisp macros


18

เมื่อมองนักตกแต่งงูหลามที่มีคนสร้างคำกล่าวไว้พวกเขามีพลังเทียบเท่ากับมาโคร 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 พลัส ธ หายไปhomoiconicity อาจทำให้การแปลงรหัสเป็นไปไม่ได้

ดังนั้นทั้งสองเปรียบเทียบกันอย่างไรและคุณสามารถบอกได้ว่าพวกเขามีความเท่าเทียมกันในสิ่งที่คุณสามารถทำได้? หลักฐานดูเหมือนจะชี้ไปที่มัน

แก้ไข:จากความคิดเห็นฉันกำลังมองหาสองสิ่ง: การเปรียบเทียบ "มีประสิทธิภาพเท่ากับ" และ "เป็นเรื่องง่ายที่จะทำสิ่งที่ยอดเยี่ยมด้วย"


12
แน่นอนว่ามัณฑนากรไม่ใช่มาโครที่แท้จริง พวกเขาไม่สามารถแปลภาษาโดยพลการ (ด้วยไวยากรณ์ที่แตกต่างกันโดยสิ้นเชิง) เป็นงูหลาม ผู้ที่อ้างสิทธิ์ตรงกันข้ามไม่เข้าใจมาโคร
SK-logic

1
Python ไม่ใช่ homoiconic แต่มันมีความไดนามิกมาก Homoiconicity นั้นจำเป็นสำหรับการแปลงรหัสที่มีประสิทธิภาพหากคุณต้องการทำในเวลารวบรวม - หากคุณมีการรองรับการเข้าถึงโดยตรงไปยัง AST ที่รวบรวมและเครื่องมือในการปรับเปลี่ยนคุณสามารถทำได้ที่รันไทม์โดยไม่คำนึงถึงไวยากรณ์ภาษา ที่ถูกกล่าวว่า "มีประสิทธิภาพเท่ากับ" และ "ง่ายต่อการทำสิ่งที่ยอดเยี่ยมด้วย" เป็นแนวคิดที่แตกต่างกันมาก
Phoshi

บางทีฉันควรเปลี่ยนคำถามเป็น "ง่าย ๆ ที่จะทำสิ่งที่ยอดเยี่ยมด้วย" แล้ว ;)
Profpatsch

บางทีบางคนสามารถแฮ็คฟังก์ชั่นการสั่งซื้อที่สูงกว่าของ Clojure กับตัวอย่าง Python ด้านบน ฉันพยายาม แต่กากบาทใจของฉันในกระบวนการ เนื่องจากตัวอย่างของ Python ใช้คุณลักษณะของวัตถุสิ่งนี้จึงต้องแตกต่างกันเล็กน้อย
Profpatsch

การดัดแปลง @Phoshi AST รวบรวมที่ใช้เวลาเป็นที่รู้จักกันในนาม: รหัสตนเองการปรับเปลี่ยน
Kaz

คำตอบ:


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

)  

ถ้าเราต้องการที่จะเพิ่มไวยากรณ์แฟนซีเช่นในหลามเราเขียนแมโครอ่าน มาโครผู้อ่านช่วยให้เราสามารถเขียนโปรแกรมในระดับของไวยากรณ์ 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))

ผู้อ่านเสียงกระเพื่อมอ่านข้างต้นเพื่อ:

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

การใช้งานดังกล่าวข้างต้นกับแมโครตัวอ่านเดียวกัน ข้อดีคือเรียบเรียงเสียงกระเพื่อมยังคงเห็นว่ามันเป็นสิ่งที่เรียกว่ารูปแบบระดับบนสุด - * การรูปแบบไฟล์คอมไพเลอร์ถือว่าระดับบนสุดพิเศษเช่นจะเพิ่มข้อมูลเกี่ยวกับพวกเขาเข้าไปรวบรวมเวลาที่สภาพแวดล้อม ในตัวอย่างข้างต้นเราจะเห็นว่ามาโครมองเข้าไปในซอร์สโค้ดและแยกชื่อ

ตัวอ่านเสียงกระเพื่อมอ่านตัวอย่างข้างต้นเป็น:

(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 (ภาษา) ผู้ตกแต่งไม่สามารถปรับเปลี่ยนฟังก์ชั่นได้เพียงแค่หุ้มมันดังนั้นมันจึงมีพลังน้อยกว่ามาโครเสียงกระเพื่อมอย่างแน่นอน

ใน CPython (ล่าม) ผู้ตกแต่งสามารถปรับเปลี่ยนฟังก์ชั่นได้เนื่องจากพวกเขามีการเข้าถึง bytecode แต่ฟังก์ชั่นจะถูกคอมไพล์ก่อนและกว่าจะสามารถเล่นกับมัณฑนากรได้ดังนั้นจึงไม่สามารถแก้ไขไวยากรณ์ได้ - เทียบเท่าจะต้องทำ

โปรดทราบว่า lisps ที่ทันสมัยไม่ได้ใช้ S-expressions เป็น bytecode ดังนั้นแมโครที่ทำงานกับรายการ S-expression จะทำงานได้อย่างแน่นอนก่อนการคอมไพล์ bytecode ดังที่ได้กล่าวไว้ข้างต้นใน python the decorator ทำงานหลังจากนั้น


1
คุณไม่จำเป็นต้องปรับเปลี่ยนฟังก์ชั่น คุณจะต้องอ่านโค้ดของฟังก์ชั่นในบางรูปแบบ (ในทางปฏิบัติหมายถึง bytecode) ไม่ว่ามันจะทำให้มันใช้งานได้จริงมากกว่านี้

2
@delnan: ในทางเทคนิคเสียงกระเพื่อมไม่ได้ปรับเปลี่ยนทั้ง; มันใช้มันเป็นแหล่งที่มาเพื่อสร้างใหม่และก็จะเป็นงูหลามใช่ ปัญหาอยู่ที่ไม่มีรายการโทเค็นหรือ AST และความจริงที่คอมไพเลอร์ได้บ่นเกี่ยวกับบางสิ่งที่คุณอาจอนุญาตในแมโคร
Jan Hudec

4

มันค่อนข้างยากที่จะใช้เครื่องมือตกแต่ง Python เพื่อแนะนำกลไกการควบคุมการไหลแบบใหม่

มันเป็นเรื่องเล็กน้อยที่จะใช้แมโคร Common LISP เพื่อแนะนำกลไกการควบคุมการไหลแบบใหม่

จากนี้อาจเป็นไปได้ว่าพวกเขาไม่ได้แสดงออกอย่างเท่าเทียมกัน (ฉันเลือกที่จะตีความ "พลัง" เป็น "แสดงออก" เนื่องจากฉันคิดว่าสิ่งที่พวกเขาหมายถึงจริง)


ฉันกล้าพูดs/quite hard/impossible/

@delnan ดีฉันจะไม่ไปค่อนข้างไกลเท่าที่จะพูดว่า "เป็นไปไม่ได้" แต่แน่นอนคุณจะต้องทำงานที่มัน
Vatine

0

มันเกี่ยวข้องกับ functionallity อย่างแน่นอน แต่จาก decorator Python มันไม่สำคัญที่จะแก้ไขวิธีการที่ถูกเรียก (นั่นคือfพารามิเตอร์ในตัวอย่างของคุณ) หากต้องการแก้ไขคุณสามารถคลั่งไคล้กับโมดูลast ) แต่คุณจะต้องอยู่ในการเขียนโปรแกรมที่ค่อนข้างซับซ้อน

สิ่งต่าง ๆ ในบรรทัดนี้ได้ทำไปแล้วลองดูแพ็คเกจmacropyสำหรับตัวอย่างที่น่าเหลือเชื่อ


3
แม้แต่astสิ่งที่เปลี่ยนแปลงใน python ก็ไม่เท่ากับแมโคร Lisp ด้วย Python ภาษาต้นฉบับควรเป็น Python ด้วย Lisp macros ภาษาต้นฉบับที่ถูกแปลงโดยแมโครสามารถเป็นอะไรก็ได้ ดังนั้น Python metaprogramming จึงเหมาะสำหรับสิ่งที่เรียบง่าย (เช่น AoP) ในขณะที่ Lisp metaprogramming นั้นมีประโยชน์สำหรับการใช้คอมไพเลอร์ eDSL ที่ทรงพลัง
SK-logic

1
สิ่งที่เป็นที่ macropy ไม่ได้ดำเนินการโดยใช้นักตกแต่ง มันใช้ไวยากรณ์มัณฑนากร (เพราะมันจะต้องใช้ไวยากรณ์หลามที่ถูกต้อง) แต่จะดำเนินการโดยการหักหลังกระบวนการรวบรวมไบต์จากเบ็ดนำเข้า
Jan Hudec

@ SK-logic: ใน Lisp ภาษาต้นฉบับต้องมีการกระเพื่อม Just Lisp syntax นั้นเรียบง่าย แต่มีความยืดหยุ่นในขณะที่ python syntax นั้นซับซ้อนกว่าและไม่ยืดหยุ่น
Jan Hudec

1
@JanHudec ในภาษาต้นฉบับ Lisp สามารถมีไวยากรณ์ใด ๆ (ฉันหมายถึงอะไรก็ได้ ) - ดูมาโครผู้อ่าน
SK-logic
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.