2.35. พื้นฐานของรูปแบบ (Pattern basics)¶
นิพจน์ปกติ (regular expression) คือภาษาขนาดเล็กสำหรับอธิบายสตริงตามรูปแบบแทนที่จะเป็นเนื้อหาที่ระบุ ใช้มันเมื่อสตริงจะตรงกันก็ต่อเมื่อเป็นไปตาม รูปแบบ ที่คุณอธิบายได้แต่ไม่สามารถระบุครบถ้วนได้ -- "ลำดับตัวเลขตามด้วยหน่วย", "บรรทัดที่ขึ้นต้นด้วย ERROR และลงท้ายด้วยตัวเลข", "นามสกุลไฟล์ใดๆ เหล่านี้ ในลำดับใดก็ได้ พร้อมคำนำหน้า v ที่เป็นทางเลือก"
ใช้ re เฉพาะเมื่อ เมธอดสตริงธรรมดาทำไม่ได้
str.startswith(),str.endswith()-- ทดสอบคำนำหน้าหรือส่วนต่อท้ายที่ตายตัวin-- ทดสอบว่ามีสตริงย่อยที่ตายตัวอยู่หรือไม่str.split(),str.find(),str.replace()-- ทำงานกับตัวคั่นที่ตายตัว
แต่ละอย่างเหล่านี้เร็วกว่า อ่านง่ายกว่า และผิดพลาดได้น้อยกว่า regex ที่เทียบเท่ากัน ใช้ regex เมื่อ รูปแบบ ของสตริงมีความสำคัญและสตริงย่อยที่ระบุไม่สำคัญ
2.35.1. สี่สิ่งที่คุณจะใช้¶
โมดูล MicroPython re มีสี่สิ่ง:
re.compile()-- แปลงสตริงรูปแบบเป็นออบเจ็กต์รูปแบบที่คอมไพล์แล้วซึ่งนำมาใช้ซ้ำได้re.match()-- ลองใช้รูปแบบที่ จุดเริ่มต้น ของสตริง รูปแบบถูกยึดที่ตำแหน่ง 0re.search()-- ลองใช้รูปแบบ ที่ใดก็ได้ ในสตริง คืนค่าการจับคู่แรกที่พบre.sub()-- ค้นหาทุกการจับคู่และแทนที่
สิ่งที่ขาดหายไปเมื่อเทียบกับ CPython ที่น่าสังเกต: ไม่มี re.findall, ไม่มี re.finditer, ไม่มี re.split ในระดับโมดูล (รูปแบบที่คอมไพล์แล้วมีเมธอด split แทน), ไม่มี re.fullmatch, ไม่มีค่าคงที่แฟล็กเช่น re.IGNORECASE สำหรับกรณีที่ต้องการใช้สิ่งเหล่านี้บน CPython ให้สร้างสิ่งที่เทียบเท่าจาก re.search() ในลูป
2.35.2. รูปแบบแรก¶
รูปแบบ r'\d+' จับคู่ตัวเลขหนึ่งตัวหรือมากกว่า:
>>> import re
>>> m = re.search(r'\d+', 'sensor reading 42 ok')
>>> m.group(0)
'42'
สิ่งที่ควรสังเกต:
รูปแบบเขียนเป็น raw string (
r'...') เพื่อให้แบ็กสแลชใน\dส่งถึงreแทนที่จะถูกประมวลผลเป็น Python string escape ใช้ raw string สำหรับรูปแบบ regex เสมอre.search()คืนค่า match object เมื่อสำเร็จและคืนค่าNoneเมื่อล้มเหลว ตรวจสอบเสมอก่อนเรียกmatch.group()m.group(0)คือข้อความทั้งหมดที่รูปแบบจับคู่ กลุ่มที่ 1, 2, ... จะปรากฏในภายหลัง เมื่อรูปแบบมีวงเล็บจับค่า
รูปแบบเดียวกันกับ re.match() คืนค่า None เพราะสตริงไม่ได้ เริ่มต้น ด้วยตัวเลข:
>>> re.match(r'\d+', 'sensor reading 42 ok') is None
True
>>> re.match(r'\d+', '42 readings')
<match num=1>
2.35.3. ส่วนประกอบของรูปแบบ¶
รูปแบบที่มีประโยชน์ส่วนใหญ่สร้างจากชุดส่วนประกอบขนาดเล็ก ส่วนที่ทำงานได้ใน MicroPython:
ตัวอักษรที่ระบุ -- ตัวอักษรใดก็ตามที่ไม่ใช่ตัวอักษรพิเศษจะจับคู่กับตัวมันเอง hello จับคู่กับ hello
ตัวอักษรพิเศษ -- . ^ $ * + ? { } [ ] \ | ( ) ทั้งหมดมีความหมายด้านล่าง หากต้องการจับคู่ตัวอักษรพิเศษตามตัวอักษรจริง ให้ใช้ backslash นำหน้า: \. จับคู่กับจุดตัวอักษรจริง
คลาสตัวอักษร -- ตัวย่อสำหรับชุดตัวอักษรทั่วไป:
\d-- ตัวเลขใดก็ได้0-9\D-- ตัวที่ไม่ใช่ตัวเลข\s-- ช่องว่างใดก็ได้ (เว้นวรรค, tab, บรรทัดใหม่)\S-- ตัวที่ไม่ใช่ช่องว่าง\w-- ตัวอักษร "คำ" ใดก็ได้: ตัวอักษร, ตัวเลข, ขีดล่าง\W-- ตัวที่ไม่ใช่ตัวอักษรคำ.-- ตัวอักษรใดก็ได้ยกเว้นบรรทัดใหม่
ตัวกำหนดจำนวน (Quantifiers) -- จำนวนครั้งที่ส่วนก่อนหน้าต้องจับคู่:
*-- ศูนย์ครั้งหรือมากกว่า (greedy)+-- หนึ่งครั้งหรือมากกว่า (greedy)?-- ศูนย์ครั้งหรือหนึ่งครั้ง{n}-- ตรงกัน n ครั้งพอดี{m,n}-- ระหว่าง m และ n ครั้ง (รวมปลายทั้งสอง)
การรวมกัน: \d{3}-\d{4} จับคู่ตัวเลขสามตัว, ขีด, ตัวเลขสี่ตัว \s+ จับคู่ช่องว่างหนึ่งตัวหรือมากกว่า hello.*world จับคู่กับ hello, อะไรก็ได้ (รวมถึงไม่มีอะไร), แล้วก็ world
Note
Greedy หมายความว่าตัวกำหนดจำนวนใช้ข้อมูลให้มากที่สุดเท่าที่ทำได้ขณะที่ยังให้ส่วนที่เหลือของรูปแบบจับคู่ได้ สำหรับ hello x world y world นั้น .* ใน hello.*world จับคู่กับช่วงที่ยาวที่สุดที่ยังเหลือ world ไว้ตอนท้าย -- จับ x world y ไม่ใช่ x ที่สั้นกว่า เช่นเดียวกับ + และรูปแบบช่วง {m,n}: เครื่องยนต์ใช้การจับคู่ที่ยาวที่สุดที่ทำได้ แล้วถอยกลับเฉพาะเมื่อส่วนที่เหลือของรูปแบบล้มเหลว
2.35.4. การแทนที่ (Substitution)¶
re.sub() ค้นหาทุกการจับคู่และแทนที่ด้วยสตริง การแทนที่สามารถอ้างอิงกลุ่มที่จับไว้ผ่าน \1, \2, ... (อธิบายพร้อมกับไวยากรณ์กลุ่มที่เหลือในภายหลัง) หากไม่มีกลุ่ม re.sub ทำหน้าที่เป็นการค้นหาและแทนที่บน regex:
>>> re.sub(r'\s+', ' ', 'too many spaces')
'too many spaces'
>>> re.sub(r'\d+', 'N', 'log 12, log 345, log 6')
'log N, log N, log N'
อาร์กิวเมนต์ที่สามคือสตริงที่จะดำเนินการ ผลลัพธ์คือสตริงใหม่ที่ทุกการจับคู่ถูกแทนที่แล้ว
2.35.5. การแยก -- เฉพาะบนรูปแบบที่คอมไพล์แล้วเท่านั้น¶
ไม่มี re.split ในระดับโมดูล หากต้องการแยกด้วย regex ให้คอมไพล์รูปแบบก่อนแล้วเรียกเมธอด split ของมัน:
>>> sep = re.compile(r'\s*,\s*')
>>> sep.split('a , b,c , d')
['a', 'b', 'c', 'd']
อาร์กิวเมนต์ที่สองที่ไม่จำเป็นจะจำกัดจำนวนการแยก:
>>> sep.split('a, b, c, d', 2)
['a', 'b', 'c, d']
2.35.6. การคอมไพล์เพื่อนำมาใช้ซ้ำ¶
หากรูปแบบเดียวกันทำงานหลายครั้ง -- ภายในลูปหรือในฟังก์ชันที่ถูกเรียกบ่อย -- คอมไพล์ครั้งเดียวและนำออบเจ็กต์ที่คอมไพล์แล้วมาใช้ซ้ำ:
digit_run = re.compile(r'\d+')
def first_number(line):
m = digit_run.search(line)
return int(m.group(0)) if m else None
การเรียก pattern.match() และ pattern.search() บนออบเจ็กต์ที่คอมไพล์แล้วเหมือนกับฟังก์ชันระดับโมดูล แต่ข้ามต้นทุนการคอมไพล์ซ้ำในทุกการเรียก
2.35.7. รูปแบบที่ไม่จับคู่กับสิ่งใด¶
สามรูปแบบโดยเฉพาะที่ทำให้นักพัฒนาพลาด:
.*จับคู่กับสตริงว่างre.search(r'.*', s).group(0)คืนค่า''สำหรับทุกข้อมูลนำเข้ารูปแบบที่มีตัวอักษรพิเศษที่ไม่ได้ใส่ backslash นำหน้าเป็น syntax error
re.compile(r'cost: $5')เกิดValueErrorเพราะ$หมายถึง "สิ้นสุดสตริง" ใช้r'cost: \$5'จุด
.ไม่ได้ จับคู่กับบรรทัดใหม่ หากต้องการจับคู่ข้ามบรรทัด ให้เขียนรูปแบบจัดการอย่างชัดเจนด้วย[\s\S]หรือป้อนทีละบรรทัด
ด้วยส่วนประกอบเหล่านี้ รูปแบบสามารถจับคู่กับส่วนย่อยของข้อความที่มี รูปแบบตายตัว ได้เกือบทุกอย่าง การดึง ข้อมูล ที่มีโครงสร้างออกจากการจับคู่ต้องใช้กลุ่มจับค่า