2.35. Cơ bản về mẫu

Biểu thức chính quy là một ngôn ngữ nhỏ để mô tả chuỗi theo hình thức thay vì theo nội dung chính xác của chúng. Hãy dùng nó khi một chuỗi khớp khi và chỉ khi nó tuân theo một mẫu mà bạn có thể mô tả nhưng không thể liệt kê -- "một dãy chữ số theo sau là một đơn vị", "một dòng bắt đầu bằng ERROR và kết thúc bằng một số", "bất kỳ phần mở rộng tệp nào trong số này, theo thứ tự bất kỳ, với tiền tố v tùy chọn".

Chỉ sử dụng re khi một phương thức chuỗi thuần không đáp ứng được.

Mỗi phương thức đó nhanh hơn, dễ đọc hơn và ít sai hơn so với regex tương đương. Dùng regex khi hình thức của chuỗi quan trọng và chuỗi con chính xác thì không.

2.35.1. Bốn thứ bạn sẽ dùng

Module MicroPython re cung cấp bốn thứ:

  • re.compile() -- biến một chuỗi mẫu thành đối tượng mẫu đã biên dịch mà bạn có thể dùng lại.

  • re.match() -- thử mẫu tại đầu của một chuỗi. Mẫu được neo tại vị trí 0.

  • re.search() -- thử mẫu bất kỳ đâu trong một chuỗi. Trả về kết quả khớp đầu tiên.

  • re.sub() -- tìm mọi kết quả khớp và thay thế chúng.

Các thiếu sót đáng chú ý so với CPython: không có re.findall, không có re.finditer, không có re.split ở cấp module (các mẫu đã biên dịch có phương thức split thay thế), không có re.fullmatch, không có hằng số cờ như re.IGNORECASE. Khi bạn muốn dùng một trong những thứ đó trên CPython, hãy xây dựng tương đương từ re.search() trong một vòng lặp.

2.35.2. Mẫu đầu tiên

Mẫu r'\d+' khớp với một hoặc nhiều chữ số:

>>> import re
>>> m = re.search(r'\d+', 'sensor reading 42 ok')
>>> m.group(0)
'42'

Một vài điểm cần chú ý:

  • Mẫu được viết dưới dạng chuỗi thô (r'...') để dấu gạch chéo ngược trong \d truyền đến re thay vì bị xử lý như ký tự thoát chuỗi Python. Luôn dùng chuỗi thô cho mẫu regex.

  • re.search() trả về đối tượng khớp khi thành công và None khi thất bại. Luôn kiểm tra trước khi gọi match.group().

  • m.group(0) là toàn bộ văn bản mà mẫu đã khớp. Nhóm 1, 2, ... xuất hiện sau, khi mẫu có chứa dấu ngoặc bắt giữ.

Cùng mẫu với re.match() trả về None vì chuỗi không bắt đầu bằng chữ số:

>>> re.match(r'\d+', 'sensor reading 42 ok') is None
True
>>> re.match(r'\d+', '42 readings')
<match num=1>

2.35.3. Các thành phần của một mẫu

Hầu hết các mẫu hữu ích được xây dựng từ một tập nhỏ các thành phần. Những thành phần hoạt động trong MicroPython:

Ký tự literal -- bất kỳ ký tự nào không đặc biệt đều khớp với chính nó. hello khớp với hello.

Ký tự đặc biệt -- . ^ $ * + ? { } [ ] \ | ( ) đều có ý nghĩa bên dưới. Để khớp một trong số chúng theo nghĩa đen, hãy thoát nó bằng dấu gạch chéo ngược: \. khớp với một dấu chấm literal.

Lớp ký tự -- phím tắt cho các tập ký tự phổ biến:

  • \d -- bất kỳ chữ số nào từ 0-9

  • \D -- bất kỳ ký tự nào không phải chữ số

  • \s -- bất kỳ ký tự khoảng trắng nào (dấu cách, tab, xuống dòng)

  • \S -- bất kỳ ký tự nào không phải khoảng trắng

  • \w -- bất kỳ ký tự "từ" nào: chữ cái, chữ số, dấu gạch dưới

  • \W -- bất kỳ ký tự nào không phải ký tự từ

  • . -- bất kỳ ký tự nào ngoại trừ dòng mới

Bộ định lượng -- số lần thành phần trước phải khớp:

  • * -- không hoặc nhiều lần (tham lam)

  • + -- một hoặc nhiều lần (tham lam)

  • ? -- không hoặc một lần

  • {n} -- chính xác n lần

  • {m,n} -- giữa mn lần (bao gồm hai đầu)

Kết hợp: \d{3}-\d{4} khớp với ba chữ số, một dấu gạch ngang, bốn chữ số. \s+ khớp với một hoặc nhiều ký tự khoảng trắng. hello.*world khớp với hello, bất kỳ thứ gì (kể cả không có gì), rồi world.

Ghi chú

Tham lam có nghĩa là bộ định lượng tiêu thụ nhiều đầu vào nhất có thể trong khi vẫn cho phép phần còn lại của mẫu khớp. Với hello x world y world, .* trong hello.*world khớp với đoạn dài nhất vẫn còn world ở cuối -- nó bắt x world y, không phải x ngắn hơn. Điều tương tự đúng với + và dạng phạm vi {m,n}: engine lấy kết quả khớp dài nhất có thể, rồi lùi lại chỉ khi phần còn lại của mẫu thất bại.

2.35.4. Thay thế

re.sub() tìm mọi kết quả khớp và thay thế chúng bằng một chuỗi. Chuỗi thay thế có thể tham chiếu các nhóm bắt giữ qua \1, \2, ... (được đề cập cùng với phần còn lại của cú pháp nhóm sau). Không có nhóm, re.sub là tìm và thay thế đơn giản trên một 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'

Tham số thứ ba là chuỗi cần thao tác; kết quả là một chuỗi mới với mọi kết quả khớp được thay thế.

2.35.5. Tách chuỗi -- chỉ trên mẫu đã biên dịch

Không có re.split ở cấp module. Để tách theo regex, hãy biên dịch mẫu trước rồi gọi phương thức split của nó:

>>> sep = re.compile(r'\s*,\s*')
>>> sep.split('a , b,c ,  d')
['a', 'b', 'c', 'd']

Tham số thứ hai tùy chọn giới hạn số lần tách:

>>> sep.split('a, b, c, d', 2)
['a', 'b', 'c, d']

2.35.6. Biên dịch để tái sử dụng

Nếu cùng một mẫu chạy nhiều lần -- bên trong một vòng lặp hoặc trong một hàm được gọi nhiều -- hãy biên dịch nó một lần và tái sử dụng đối tượng đã biên dịch:

digit_run = re.compile(r'\d+')

def first_number(line):
    m = digit_run.search(line)
    return int(m.group(0)) if m else None

Gọi pattern.match()pattern.search() trên đối tượng đã biên dịch giống như các hàm cấp module nhưng bỏ qua chi phí biên dịch lại trong mỗi lần gọi.

2.35.7. Các mẫu không khớp bất kỳ thứ gì

Ba mẫu cụ thể thường gây nhầm lẫn cho các lập trình viên:

  • .* khớp với chuỗi rỗng. re.search(r'.*', s).group(0) trả về '' với bất kỳ đầu vào nào.

  • Một mẫu có ký tự đặc biệt không được thoát là lỗi cú pháp. re.compile(r'cost: $5') gây ra ValueError$ có nghĩa là "cuối chuỗi". Dùng r'cost: \$5'.

  • Dấu chấm . không khớp với dòng mới. Để khớp qua các dòng mới, hãy viết mẫu để xử lý chúng tường minh với [\s\S] hoặc đưa vào từng dòng một.

Với những thành phần này, một mẫu có thể khớp với hầu như bất kỳ đoạn văn bản có dạng cố định nào. Để trích xuất dữ liệu có cấu trúc từ kết quả khớp, cần sử dụng nhóm bắt giữ.