2.36. Nhóm và neo

Một mẫu có thể làm nhiều hơn là chỉ nói "chuỗi này khớp" -- nó có thể tách các phần khớp ra và trả từng phần cho ứng dụng theo tên. Dấu ngoặc đơn bao quanh một phần của mẫu biến nó thành nhóm bắt giữ; đối tượng khớp sau đó cung cấp từng nhóm như một chuỗi con riêng biệt.

2.36.1. Nhóm bắt giữ

Bao bất kỳ phần nào của mẫu trong (...) để bắt giữ những gì nó khớp:

>>> 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'
  • Nhóm 0 luôn là toàn bộ kết quả khớp.

  • Nhóm 1, 2, ... là các chuỗi con được bắt giữ, được đánh số từ trái sang phải theo dấu ngoặc mở bên trái của chúng.

  • Gọi match.group() với chỉ số vượt quá nhóm cuối cùng sẽ gây ra IndexError.

Một mẫu phổ biến là "khớp một cấu trúc đã biết, bắt giữ các phần biến đổi dưới dạng số nguyên":

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. Nhóm không bắt giữ

Dấu ngoặc đơn cũng nhóm một biểu thức con để một bộ định lượng có thể áp dụng cho toàn bộ nhóm. Đó là mục đích duy nhất của việc nhóm trong r'(ab)+' -- "một hoặc nhiều ab". Việc ab xuất hiện như nhóm 1 chỉ là tác dụng phụ.

Để nhóm mà không bắt giữ, dùng (?:...)

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

Nhóm không bắt giữ giúp giữ số thứ tự nhóm gọn gàng khi một mẫu sử dụng nhóm cho cấu trúc nhưng không cần trích xuất từng phần ra.

2.36.3. Neo

Neo không khớp với một ký tự -- chúng khớp với một vị trí.

  • ^ -- đầu chuỗi.

  • $ -- cuối chuỗi.

Neo là thứ làm cho re.match()re.search() hoạt động khác nhau. re.match(p, s) tương đương với re.search('^' + p, s): nó buộc mẫu bắt đầu tại vị trí 0. Thêm $ vào cuối mẫu khiến mẫu khớp với toàn bộ chuỗi và không gì khác:

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

^$ trong MicroPython re luôn có nghĩa là đầu và cuối của toàn bộ chuỗi được truyền vào re.search(). Không có cờ re.MULTILINE để khiến chúng khớp tại mỗi dấu xuống dòng nhúng, và $ cũng không khớp với vị trí trước \n ở cuối -- nó phải là cuối tuyệt đối của đầu vào. Để lấy hành vi theo từng dòng, hãy tách đầu vào theo dấu xuống dòng trước rồi chạy mẫu trên từng dòng.

2.36.4. Tập ký tự

Dấu ngoặc vuông định nghĩa một tập ký tự tường minh. Kết quả khớp tiêu thụ đúng một ký tự từ tập đó.

  • [abc] -- một trong a, b, c.

  • [a-z] -- một ký tự trong phạm vi a-z (bao gồm hai đầu).

  • [a-zA-Z0-9] -- chữ cái hoặc chữ số. Kết hợp ba phạm vi.

  • [^abc] -- không phải một trong a, b, c. ^ chỉ phủ định khi nó là ký tự đầu tiên bên trong dấu ngoặc vuông.

Ví dụ:

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

Lần gọi đầu tiên trả về None trong thực tế vì văn bản là chữ thường. MicroPython re không có cờ re.IGNORECASE -- để khớp không phân biệt hoa thường, hãy viết cả hai trường hợp vào tập:

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

Các phím tắt lớp ký tự (\d, \s, \w, và các dạng phủ định của chúng) cũng có thể được dùng bên trong [...]: [\w-] là "ký tự từ hoặc dấu gạch ngang".

2.36.5. Bộ định lượng tham lam so với lười biếng

Các bộ định lượng *, +, ?{m,n} mặc định là tham lam -- chúng khớp nhiều ký tự nhất có thể trong khi phần còn lại của mẫu vẫn khớp được. Thường đó là điều được mong muốn; đôi khi thì không:

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

Bộ định lượng tham lam .+ đã bắt tất cả đến > cuối cùng. Thêm ? làm cho bộ định lượng lười biếng -- nó khớp ít nhất có thể:

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

Dạng lười biếng dừng lại ở > đầu tiên. Bộ định lượng lười biếng xuất hiện thường xuyên khi trích xuất các dấu phân cách cân bằng từ một chuỗi.

2.36.6. Tham chiếu ngược trong thay thế

re.sub() có thể tham chiếu lại các nhóm bắt giữ trong chuỗi thay thế qua \1, \2, ... Việc thay thế viết lại mỗi kết quả khớp bằng các phần đã bắt giữ:

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

Mỗi kết quả khớp bắt giữ hai số, và phép thay thế hoán đổi chúng. \g<1> là cú pháp thay thế cho cùng điều đó -- hữu ích khi ký tự tiếp theo trong chuỗi thay thế là một chữ số (r'\g<1>0' để thêm số không vào cuối nhóm 1 thay vì đọc là "nhóm 10").

2.36.7. Những gì không khả dụng

Nhắc nhở về những gì MicroPython re không hỗ trợ, trong trường hợp một mẫu từ CPython xuất hiện ở đây và gây bất ngờ cho bạn:

  • Lookahead (?=...) và lookbehind (?<=...) -- chưa được triển khai.

  • Nhóm có tên (?P<name>...) và tham chiếu ngược có tên (?P=name) -- chưa được triển khai.

  • Các hằng số cờ như re.IGNORECASE, re.MULTILINE, re.DOTALL -- không được hỗ trợ. Hãy tự xây dựng tập không phân biệt hoa thường hoặc tách đầu vào trước.

  • Các phương thức match.groups(), match.span(), match.start()match.end() bị giới hạn ở mức ROM mà không board OpenMV nào được xuất xưởng bật. Code dựa vào chúng sẽ không chạy trên cam.

Với các mẫu, nhóm và neo, bộ công cụ regex trên cam đủ nhỏ để học trong một buổi và đủ phong phú để làm mọi thứ ngoại trừ phân tích cú pháp nhạy ngữ cảnh.