2.36. กลุ่มและจุดยึด

รูปแบบสามารถทำได้มากกว่าแค่บอกว่า "สตริงนี้ตรงกัน" -- มันสามารถแยกชิ้นส่วนที่ตรงกันออกและส่งแต่ละส่วนให้แอปพลิเคชันตามชื่อ วงเล็บรอบส่วนหนึ่งของรูปแบบจะทำให้กลายเป็น กลุ่มจับค่า (capturing group) และออบเจ็กต์การจับคู่จะแสดงแต่ละกลุ่มเป็นสตริงย่อยแยกต่างหาก

2.36.1. กลุ่มจับค่า (Capturing groups)

ใส่วงเล็บ (...) รอบส่วนใดก็ได้ของรูปแบบเพื่อจับสิ่งที่มันตรงกัน:

>>> import re
>>> m = re.search(r'temp (\d+) at (\d+)s', 'temp 42 at 137s ok')
>>> m.group(0)
'temp 42 at 137s'
>>> m.group(1)
'42'
>>> m.group(2)
'137'
  • กลุ่มที่ 0 คือการจับคู่ทั้งหมดเสมอ

  • กลุ่มที่ 1, 2, ... คือสตริงย่อยที่ถูกจับ โดยนับจากซ้ายไปขวาตาม วงเล็บเปิด ของแต่ละกลุ่ม

  • การเรียก match.group() ด้วยดัชนีที่เกินกลุ่มสุดท้ายจะทำให้เกิด IndexError

รูปแบบทั่วไปคือ "จับคู่โครงสร้างที่รู้จัก แล้วจับส่วนที่แปรผันเป็น int":

def parse_temp(line):
    m = re.search(r'temp (\d+) at (\d+)s', line)
    if not m:
        return None
    return int(m.group(1)), int(m.group(2))

2.36.2. กลุ่มที่ไม่จับค่า (Non-capturing groups)

วงเล็บยังสามารถ จัดกลุ่ม นิพจน์ย่อยเพื่อให้ตัวกำหนดจำนวน (quantifier) ทำงานกับกลุ่มทั้งหมดได้ นั่นคือจุดประสงค์เดียวของการจัดกลุ่มใน r'(ab)+' -- "หนึ่งครั้งหรือมากกว่าของ ab" การที่ ab ปรากฏเป็นกลุ่มที่ 1 เป็นเพียงผลข้างเคียง

หากต้องการจัดกลุ่มโดยไม่จับค่า ให้ใช้ (?:...)

>>> re.search(r'(?:ab)+', 'xababy').group(0)
'abab'

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

2.36.3. จุดยึด (Anchors)

จุดยึดไม่ได้จับคู่กับตัวอักษร -- แต่จับคู่กับ ตำแหน่ง

  • ^ -- จุดเริ่มต้นของสตริง

  • $ -- จุดสิ้นสุดของสตริง

จุดยึดคือสิ่งที่ทำให้ re.match() และ re.search() ทำงานต่างกัน re.match(p, s) เหมือนกับ re.search('^' + p, s) ตรงที่บังคับให้รูปแบบเริ่มต้นที่ตำแหน่ง 0 การเพิ่ม $ ท้ายรูปแบบจะทำให้รูปแบบจับคู่กับสตริง ทั้งหมด และไม่มีอะไรอื่น:

>>> re.search(r'^\d+$', '12345')
<match num=1>
>>> re.search(r'^\d+$', '12345 ok') is None
True

^ และ $ ใน MicroPython re หมายถึงจุดเริ่มต้นและจุดสิ้นสุดของสตริง ทั้งหมด ที่ส่งให้ re.search() เสมอ ไม่มีแฟล็ก re.MULTILINE เพื่อให้ทำงานกับทุกบรรทัดที่ฝังอยู่ และ $ ก็ไม่จับคู่กับตำแหน่งก่อน \n ท้าย -- ต้องเป็นจุดสิ้นสุดแบบสัมบูรณ์ของข้อมูล หากต้องการพฤติกรรมแบบรายบรรทัด ให้แยกข้อมูลที่บรรทัดใหม่ก่อน แล้วรันรูปแบบบนแต่ละบรรทัด

2.36.4. ชุดตัวอักษร (Character sets)

วงเล็บเหลี่ยมกำหนดชุดตัวอักษรที่ระบุไว้อย่างชัดเจน การจับคู่จะใช้ตัวอักษรหนึ่งตัวจากชุดนั้น

  • [abc] -- หนึ่งในบรรดา a, b, c

  • [a-z] -- ตัวอักษรหนึ่งตัวในช่วง a-z (รวมปลายทั้งสอง)

  • [a-zA-Z0-9] -- ตัวอักษรหรือตัวเลข รวมสามช่วง

  • [^abc] -- ไม่ใช่ หนึ่งในบรรดา a, b, c โดย ^ จะกลับความหมายก็ต่อเมื่ออยู่ตำแหน่งแรกภายในวงเล็บเท่านั้น

ตัวอย่าง:

>>> re.search(r'[A-F0-9]{6}', 'colour #1a2b3c rest').group(0)
'1A2B3C'
>>> re.search(r'[A-F0-9]{6}', 'colour #1a2b3c rest') is None
True

การเรียกแรกคืนค่า None ในทางปฏิบัติเพราะข้อความที่ระบุเป็นตัวพิมพ์เล็ก MicroPython re ไม่มีแฟล็ก re.IGNORECASE -- หากต้องการจับคู่โดยไม่สนตัวพิมพ์ ให้เขียนทั้งสองกรณีลงในชุดตัวอักษร:

>>> re.search(r'[A-Fa-f0-9]{6}', 'colour #1a2b3c rest').group(0)
'1a2b3c'

คลาสตัวอักษรย่อ (\d, \s, \w และรูปแบบนิเสธ) สามารถใช้ภายใน [...] ได้เช่นกัน: [\w-] หมายถึง "ตัวอักษรคำหรือยัติภังค์ตัวอักษรจริง"

2.36.5. Greedy vs lazy quantifiers

ตัวกำหนดจำนวน *, +, ? และ {m,n} เป็นแบบ greedy โดยค่าเริ่มต้น -- จับคู่ตัวอักษรให้มากที่สุดเท่าที่ส่วนที่เหลือของรูปแบบยังอนุญาต บ่อยครั้งนั่นคือสิ่งที่ต้องการ แต่บางครั้งก็ไม่ใช่:

>>> re.search(r'<(.+)>', 'a <b> <c> d').group(1)
'b> <c'

แบบ greedy .+ จับคู่ไปจนถึง > ตัวสุดท้าย การต่อ ? เข้าไปทำให้ตัวกำหนดจำนวนเป็นแบบ lazy -- จับคู่ให้น้อยที่สุดเท่าที่เป็นไปได้:

>>> re.search(r'<(.+?)>', 'a <b> <c> d').group(1)
'b'

รูปแบบ lazy จะหยุดที่ > ตัวแรก Lazy quantifiers ปรากฏบ่อยมากเมื่อดึงตัวคั่นแบบสมดุลออกจากสตริง

2.36.6. การอ้างอิงย้อนกลับในการแทนที่ (Backreferences in substitution)

re.sub() สามารถอ้างถึงกลุ่มที่จับไว้ในสตริงการแทนที่ผ่าน \1, \2, ... การแทนที่จะเขียนซ้ำทุกการจับคู่โดยใช้ชิ้นส่วนที่จับไว้:

>>> re.sub(r'(\d+)\.(\d+)', r'\2.\1', 'swap 12.34 and 5.6')
'swap 34.12 and 6.5'

แต่ละการจับคู่จับตัวเลขสองตัวและการแทนที่จะสลับตำแหน่ง \g<1> เป็นไวยากรณ์ทางเลือกสำหรับสิ่งเดียวกัน -- มีประโยชน์เมื่อตัวอักษรถัดไปในการแทนที่เป็นตัวเลข (r'\g<1>0' เพื่อต่อศูนย์ตัวอักษรจริงเข้าท้ายกลุ่มที่ 1 แทนที่จะอ่านเป็น "กลุ่มที่ 10")

2.36.7. สิ่งที่ไม่รองรับ

การแจ้งเตือนเกี่ยวกับสิ่งที่ MicroPython re ไม่ รองรับ กรณีที่รูปแบบจาก CPython มาใช้ที่นี่แล้วทำให้คุณแปลกใจ:

  • Lookahead (?=...) และ lookbehind (?<=...) -- ไม่ได้นำไปใช้งาน

  • กลุ่มที่มีชื่อ (?P<name>...) และการอ้างอิงย้อนกลับที่มีชื่อ (?P=name) -- ไม่ได้นำไปใช้งาน

  • ค่าคงที่แฟล็กเช่น re.IGNORECASE, re.MULTILINE, re.DOTALL -- ไม่ถูกปฏิบัติตาม สร้างชุดที่ไม่สนตัวพิมพ์หรือแยกข้อมูลไว้ล่วงหน้าด้วยตัวเอง

  • เมธอด match.groups(), match.span(), match.start() และ match.end() ถูกควบคุมโดยระดับ ROM ที่บอร์ด OpenMV ที่จำหน่ายไม่มีบอร์ดใดเปิดใช้งาน โค้ดที่พึ่งพาเมธอดเหล่านี้จะไม่ทำงานบนกล้อง

ด้วยรูปแบบ กลุ่ม และจุดยึด ชุดเครื่องมือ regex บนกล้องนั้นมีขนาดเล็กพอที่จะเรียนรู้ได้ในครั้งเดียวและมีความสมบูรณ์พอที่จะทำได้ทุกอย่างยกเว้นการแยกวิเคราะห์แบบ context-sensitive