3 คะแนน โดย GN⁺ 2024-03-21 | 1 ความคิดเห็น | แชร์ทาง WhatsApp

ทำความเข้าใจการทำงานของอักขระ "$" ในนิพจน์ทั่วไปของ Python

  • เมื่อใช้โมดูล re ของ Python โดยทั่วไปเข้าใจกันว่า ^ หมายถึง "จุดเริ่มต้นของสตริง" และ $ หมายถึง "จุดสิ้นสุดของสตริง"
  • อย่างไรก็ตาม $ ไม่ได้หมายถึงเพียง "จุดสิ้นสุดของสตริง" เสมอไป และการทำงานอาจแตกต่างกันไปตามแพลตฟอร์ม
  • ใน Python เมื่อไม่ได้เปิดโหมดหลายบรรทัด $ สามารถจับคู่กับจุดสิ้นสุดของสตริง หรือก่อนอักขระขึ้นบรรทัดใหม่ที่ท้ายสตริงได้

ความแตกต่างระหว่างการจับคู่จุดสิ้นสุดของสตริงกับอักขระขึ้นบรรทัดใหม่

  • เมื่อปิดโหมดหลายบรรทัด ใน Python หากต้องการจับคู่กับจุดสิ้นสุดของสตริงโดยไม่มีอักขระขึ้นบรรทัดใหม่ ไม่ควรใช้เพียง $ เท่านั้น
  • สามารถใช้ \z และ \Z เพื่อจับคู่กับจุดสิ้นสุดของสตริงได้
  • ใน Python เมื่อใช้ re.MULTILINE แล้ว $ จะจับคู่ทั้งกับจุดสิ้นสุดของสตริงและจุดสิ้นสุดของแต่ละบรรทัด (ก่อนอักขระขึ้นบรรทัดใหม่ทันที)

เปรียบเทียบการทำงานของนิพจน์ทั่วไปบนแพลตฟอร์มต่าง ๆ

  • จากตารางที่เปรียบเทียบว่ารูปแบบตรงกับ cat\n หรือไม่ในหลายแพลตฟอร์ม จะเห็นได้ว่าหากอนุญาตให้จับคู่โดยรวมอักขระขึ้นบรรทัดใหม่ด้วย การใช้ $ ในโหมดหลายบรรทัดจะให้พฤติกรรมที่สอดคล้องกัน
  • หากต้องการจับคู่โดยไม่รวมอักขระขึ้นบรรทัดใหม่ ทุกแพลตฟอร์มยกเว้น Python และ ECMAScript ควรใช้ \z ส่วนใน Python และ ECMAScript ควรใช้ \Z หรือใช้ $ โดยไม่เปิดโหมดหลายบรรทัดตามลำดับ

ความเห็นของ GN⁺

  • บทความนี้ช่วยเตือนนักพัฒนาที่ใช้นิพจน์ทั่วไปให้ระวังพฤติกรรมที่คาดไม่ถึงของอักขระ $ ใน Python
  • นิพจน์ทั่วไปเป็นเครื่องมือทรงพลังมากสำหรับการประมวลผลสตริง แต่ก็ควรระวังเพราะพฤติกรรมอาจต่างกันในแต่ละแพลตฟอร์ม
  • นักพัฒนาควรตระหนักถึงความแตกต่างเหล่านี้ และอาจต้องทดสอบเพิ่มเติมเพื่อหลีกเลี่ยงปัญหาความเข้ากันได้เมื่อพัฒนาแอปพลิเคชันข้ามแพลตฟอร์ม
  • ไลบรารีนิพจน์ทั่วไปอื่นที่มีความสามารถคล้ายกัน เช่น java.util.regex ของ Java และ System.Text.RegularExpressions ของ .NET ก็จำเป็นต้องทำความเข้าใจความแตกต่างของพฤติกรรมตามแพลตฟอร์มของตนเช่นกัน
  • เมื่อนำไวยากรณ์หรือพฤติกรรมใหม่ของนิพจน์ทั่วไปมาใช้ ควรพิจารณาความเข้ากันได้กับโค้ดเดิม ผลกระทบด้านประสิทธิภาพ และช่วงการเรียนรู้ภายในทีม พร้อมประเมินทั้งประโยชน์และต้นทุนของการเปลี่ยนแปลงนั้นอย่างรอบคอบ

1 ความคิดเห็น

 
GN⁺ 2024-03-21
ความเห็นจาก Hacker News
  • คนที่คุ้นเคยกับ regular expression จะรู้ว่า ^ หมายถึง "จุดเริ่มต้นของสตริง" และ $ หมายถึง "จุดสิ้นสุดของสตริง" แต่โดยส่วนตัวแล้วฉันมองสิ่งเหล่านี้เป็น "จุดเริ่มต้นของบรรทัด" และ "จุดสิ้นสุดของบรรทัด" มากกว่า ส่วนใหญ่แล้วเราจัดการข้อความทีละบรรทัด ผลลัพธ์จึงเหมือนกัน แต่มุมมองเวลาเราคิดถึงโอเปอเรเตอร์เหล่านี้ไม่เปลี่ยนไป น่าจะเป็นเพราะฉันรู้จัก regular expression ครั้งแรกผ่าน grep และมักคิดถึงอินพุตในฐานะ "บรรทัด" เป็นหลัก

    • POSIX regular expression กับ Python regular expression ไม่เหมือนกัน โดยทั่วไปควรอ้างอิงเอกสาร regular expression ของ implementation ที่ใช้อยู่ เพราะไวยากรณ์ไม่ได้เป็นสากล
    • ตาม POSIX บทที่ 9 regular expression โดยทั่วไปเกี่ยวข้องกับการประมวลผลข้อความ และทำงานกับสตริงที่ลงท้ายด้วย NUL ซึ่งใช้แทนจุดสิ้นสุดของสตริง ยูทิลิตีบางตัวจำกัดการประมวลผลไว้ที่ระดับบรรทัด $ อาจหมายถึงจุดสิ้นสุดของสตริงหรือจุดสิ้นสุดของบรรทัดก็ได้ ซึ่งกำหนดโดยยูทิลิตี (หรือโหมด) นั้น ๆ ยูทิลิตีทั่วไปส่วนใหญ่ (grep, sed, awk, Python ฯลฯ) โดยค่าเริ่มต้นจะมองว่าเป็นจุดสิ้นสุดของบรรทัด
    • ไม่มีไวยากรณ์ regular expression แบบสากลเพียงหนึ่งเดียว ถ้าไม่รู้ภาษาและออปชันที่ใช้อยู่ ก็ไม่สามารถอ่านหรือเขียน regular expression ได้อย่างเชื่อถือ
  • นี่เป็นโอกาสสมบูรณ์แบบที่จะพูดถึง Robert Elder เขาทำคอนเทนต์บน YouTube และเขียนบล็อก มีซีรีส์เกี่ยวกับ regular expression และเจาะลึกความแตกต่างของพฤติกรรมระหว่างเครื่องมือต่าง ๆ

    • คอนเทนต์ล่าสุดของเขาก็ยอดเยี่ยมเช่นกัน: https://www.youtube.com/watch?v=ys7yUyyQA-Y
    • เขามีคอนเทนต์อีกมากที่ผู้ใช้ HN น่าจะสนใจ เช่น ความเป็นจริงและความลำบากของงานที่ปรึกษา
  • regular expression เป็นหนึ่งในสิ่งแรก ๆ ที่ฉันซึมซับได้จริงตอนเริ่มเรียน Perl (Perl ยังมีที่อบอุ่นในใจฉันเสมอเพราะหนังสือ "Camel")

    • ทุกวันนี้ข้อมูลที่สำคัญที่สุดคือ ต้องรู้ว่าแต่ละ implementation ต่างกัน และควรสร้างนิสัยในการหยิบเอกสารอ้างอิงของสิ่งที่กำลังทำอยู่ขึ้นมาดู
    • ตัวอย่างเช่น Emacs regular expression ใช้ "\s_-" (หรืออะไรสักอย่างบนหน้าจอถ้าไม่มีเอกสารอ้างอิง) เป็น character class แทน "\w" แต่ Emacs ก็มีทั้งเอกสารและความสามารถในการค้นพบที่ยอดเยี่ยมที่สุด
    • ยูทิลิตีบางตัวต้อง escape วงเล็บ บางตัวไม่ต้อง บางครั้งพฤติกรรมนี้ปรับแต่งได้ และบางครั้งก็ไม่ได้
    • ฉันผ่านทั้งช่วงสับสน หงุดหงิด และปฏิเสธมาแล้ว ตอนนี้ก็แค่ยอมรับมัน แนวคิดเหมือนกันทุกที่ แต่รายละเอียดปลีกย่อยต่างกัน
  • ฟังออกเลยว่าผู้จัดการฝ่ายจ้างงานแย่ ๆ จะเพิ่มคำถามว่า 'จะ match จุดสิ้นสุดของสตริงใน regular expression อย่างไร?' เข้าไปในรายการคำถามประเภท 'ฮ่า! แกไม่รู้มุกหลอกนี่นา!'

  • มันแปลกที่พูดเรื่อง regular expression แล้วกลับไม่ใส่ Perl ไว้ในรายการ

    • คำอธิบายของ $ ในเอกสาร perlre: match ที่จุดสิ้นสุดของสตริง (หรือก่อนอักขระขึ้นบรรทัดใหม่ที่ท้ายสตริง; หรือก่อนอักขระขึ้นบรรทัดใหม่ทุกตัวหากใช้ /m)
  • Raku (เดิมคือ Perl 6) เลือกใช้ ^ และ $ เพื่อแทนจุดเริ่มต้นและจุดสิ้นสุดของสตริง และเพิ่ม ^^ กับ $$ เพื่อแทนจุดเริ่มต้นและจุดสิ้นสุดของบรรทัด ทำให้ไม่ต้องมีหรือไม่จำเป็นต้องใช้โหมดหลายบรรทัด

    • ข้อดีอย่างหนึ่งของการคิดใหม่/เขียนใหม่ทั้งหมด คือสามารถเรียนรู้จากข้อเท็จจริงที่ว่าพฤติกรรมแบบเดิมเคยทำให้คนประหลาดใจ
  • มีใครคิดจริง ๆ เหรอว่า regular expression ถูกทำให้เป็นมาตรฐานแล้ว? การย้ายไปยังบริบทใหม่คือกระบวนการเรียนรู้ใหม่เสมอ

  • มีความสับสนระหว่างสตริงกับบรรทัดอยู่ สตริงคือชุดต่อเนื่องของอักขระ ส่วนบรรทัดอาจหมายถึงได้สองแบบ ถ้ามองว่าอักขระขึ้นบรรทัดใหม่เป็นตัวจบบรรทัด บรรทัดก็คือชุดต่อเนื่องของอักขระที่ไม่ใช่อักขระขึ้นบรรทัดใหม่รวมกับอักขระขึ้นบรรทัดใหม่ ถ้าไม่มีอักขระขึ้นบรรทัดใหม่ ก็ยังไม่ใช่บรรทัดที่สมบูรณ์ แบบนี้คือสิ่งที่ POSIX ใช้ ถ้ามองว่าอักขระขึ้นบรรทัดใหม่เป็นตัวคั่นบรรทัด บรรทัดก็คือชุดต่อเนื่องของอักขระที่ไม่ใช่อักขระขึ้นบรรทัดใหม่ ไม่ว่าแบบไหน เนื้อหาของบรรทัดก็จบก่อนอักขระขึ้นบรรทัดใหม่ เพราะมันเป็นตัวจบบรรทัดหรือเป็นตัวคั่นจากบรรทัดถัดไป

    • ความหมายของ ^ และ $ ยึดตาม "บรรทัด" ไม่ว่าจะอยู่ในโหมดบรรทัดเดียวหรือหลายบรรทัดก็ตาม ส่วนความหมายที่ยึดตาม "สตริง" — ซึ่งเวลาใช้กับไฟล์อาจตีความเป็นทั้งไฟล์ — จะใช้ \A และ \Z หรือสิ่งที่เทียบเท่ากัน
  • เรื่องนี้เคยนำไปสู่บั๊กร้ายแรงหลายอย่างในแอปที่ใช้ Ruby ดังนั้นฉันใช้ \A\z เสมอ