1. Lời dẫn
Trong Python, chuỗi kí tự (string) không chỉ đơn giản là văn bản để hiển thị ra màn hình mà còn là dữ liệu, tham số quan trọng của nhiều các bài toán thực tế như: dữ liệu do người dùng nhập vào, dữ liệu để phân tích, tạo báo cáo, xây dựng ứng dụng.
Khả năng xử lí chuỗi trong ngôn ngữ Python khá mạnh với nhiều hàm được cung cấp sắn và cũng rất dễ đọc, dễ hiểu, dễ viết.
Bài viết này sẽ giúp bạn hiểu và làm chủ các thao tác xử lí chuỗi trong Python từ nền tảng đến ứng dụng, thông qua các nội dung diễn giải và ví dụ minh họa thiết thực.

2. Các cách tạo chuỗi kí tự trong Python
2.1. Tạo chuỗi kí tự tiêu chuẩn
Đây là cách đơn giản và phổ biến nhất để khai báo chuỗi văn bản trong Python, bạn có thể chọn một trong hai cách sau:
- Dấu nháy đơn
'...' - Dấu nháy kép
"..."
text1 = 'Hello AI VietNam'
text2 = "Hello AI VietNam"
Hai câu lệnh này đều tạo ra biến chuỗi giống nhau. Vậy tại sao Python lại cho người dùng 2 cách, đơn giản là nhờ đó mà đoạn mã Python dễ đọc hơn, làm cho chuỗi trong Python được viết tự nhiên như cách viết của con người hơn và đặc biệt là giảm việc sử dụng kí tự thoát \ trong chuỗi khi cần biểu diễn các kí tự đặc biệt.
Nguyên tắc :
- Bên trong có nháy đơn
'→ dùng nháy kép bên ngoài" - Bên trong có nháy kép
"→ dùng nháy đơn bên ngoài'
Ví dụ với câu I'm learning Python, nếu chỉ có một cách dùng kí tự ', chúng ta phải viết 'I\'m learning Python' (sử dụng kí tự thoát \), nhưng do Python hỗ trợ 2 cách viết nên chúng ta chỉ cần phối hợp sentence = "I'm learning Python"
2.2. Tạo chuỗi thoát (escape sequences)
Trong máy tính, không phải mọi kí tự đều là văn bản, chữ số hoặc ký hiệu, biểu tượng có thể nhận biết được (A, B, 1, 2, @, #,…) mà còn có các kí tự điều khiển (control characters). Đây là các kí tự dùng để:
- Xuống dòng
- Căn lề (tab)
- Báo hiệu kết thúc dòng
- ...
Những kí tự đặc biệt này không có hình dạng hiển thị rõ ràng, nên khi viết trong chuỗi, Python (và nhiều ngôn ngữ khác) dùng chuỗi thoát, tức là một kí hiệu bắt đầu bằng dấu \ để biểu diễn.
Hay nói cách khác, chuỗi thoát (escape sequences) trong Python chính là cách chúng ta viết lại các kí tự điều khiển trong bảng mã ASCII. Đó là các kí tự đặc biệt có giá trị 0-31 và 127 trong bảng mã ASCII.
Các kí tự thoát phổ biến
| Ký tự | Ý nghĩa |
|---|---|
\n |
Xuống dòng |
\t |
Tab |
\" |
Dấu nháy kép |
\' |
Dấu nháy đơn |
\\ |
Dấu gạch chéo |
Ví dụ đoạn mã sau:
print("I love \nAI Vietnam")
Sẽ hiển thị kết quả như sau:
I love
AI Vietnam
Chuỗi thoát đặc biệt quan trọng khi bạn muốn:
- In nhiều dòng văn bản
- Định dạng báo cáo
- Ghi tệp .txt
- Nhật kí hệ thống (Log)
Trong trường hợp bạn không muốn Python xử lí chuỗi thoát, bạn có thể dùng cách khác để tạo chuỗi đặc biệt. Đó là tạo chuỗi thô (raw string) - phần tiếp theo.
2.3. Tạo chuỗi kí tự thô
Chuỗi ký tự thô (raw string) là chuỗi mà Python giữ nguyên từng ký tự bạn gõ, đúng như cách các ký tự xuất hiện trên bàn phím.
Để tạo chuỗi kí tự thô, bạn chỉ cần thêm kí tự r vào ngay trước chuỗi văn bản.
Ví dụ:
path = r"C:\new_folder\data\test.txt"
print(path)
# Kết quả: 'C:\new_folder\data\test.txt'
Vậy nếu dùng chuỗi thoát thì bạn phải viết như thế nào? Bạn sẽ viết như sau:
path = "C:\\new_folder\\test.txt"
Bạn thấy sao, không tự nhiên đúng không, và đó là lý do chuỗi thô (raw string) ra đời.
Có một lưu ý là chuỗi thô không được kết thúc bằng dấu \.
Nghĩa là nếu bạn viết path = r"C:\temp\" thì sẽ báo lỗi do lúc này hệ thống hiểu dấu \ là ký tự thoát. Trong trường hợp này cách xử lý sẽ là: path = r"C:\temp\\" (thêm ký tự thoát trước ký tự \)
2.4. Tạo chuỗi kí tự định dạng
Trong thực tế, chuỗi hiếm khi đứng một mình mà thường đi kèm với: giá trị của biến, kết quả tính toán, dữ liệu input người dùng nhập vào,... Ví dụ như bạn muốn in ra câu Lan đạt điểm 9.5, trong đó Lan và 9.5 là giá trị của biến name và score, nếu không có chuỗi định dạng, bạn sẽ phải viết:
print(name + " đạt điểm " + str(score))
Nhưng với chuỗi định dạng bắt đầu bằng f, bạn chỉ cần viết:
print(f"{name} đạt điểm {score}")
Cú pháp bạn viết sẽ gọn hơn rất nhiều mà cũng không cần phải đổi kiểu dữ liệu.
Ngoài ra, Python còn cho phép bạn chèn trực tiếp biểu thức trong chuỗi định dạng mà không phải tạo biến trung gian hay phải định dạng phức tạp.
age = 20
print(f"Next year you will be {age + 1}")
# Kết quả: 'Next year you will be 21'
Một điểm đặc biệt nữa của Python là trong chuỗi định dạng, bạn còn có thể gọi trực tiếp hàm, ví dụ:
name = " python "
print(f"Name: {name.strip().title()}")
# Kết quả: 'Name: Python'
Chuỗi định dạng còn được dùng trong định dạng số như dấu phân cách hàng ngàn, số chữ số thập phân để cho dễ đọc hơn.
money = 123456789
print(f"{money:,}")
# Kết quả: '123,456,789'
pi = 3.14159265
print(f"{pi:.2f}")
# Kết quả: '3.14'
2.5. Tạo chuỗi kí tự bằng hàm str()
Có thể nói trong Python, hàm str() là hàm xây dựng chuỗi (string constructor). Với Cú pháp str(object), trong đó object là bất kỳ giá trị hoặc đối tượng nào. Do Python không tự đổi kiểu dữ liệu sang chuỗi nên trong các lệnh print() hoặc các biểu thức nối chuỗi, bạn phải đổi các biến, giá trị khác chuỗi về giá trị chuỗi bằng cách dùng hàm str().
price = 50000
print("Giá sản phẩm: " + str(price) + " VND")
# Kết quả: 'Giá sản phẩm: 50000 VND'
3. Các toán tử chuỗi kí tự
Trong Python, chuỗi kí tự không chỉ dùng để lưu trữ văn bản mà còn có thể kết hợp với một số toán tử đặc biệt. Những toán tử này cho phép chúng ta nối chuỗi, lặp chuỗi hoặc kiểm tra sự tồn tại của chuỗi con bên trong một chuỗi lớn hơn. Phần này sẽ lần lượt trình bày cách hoạt động của các toán tử phổ biến khi làm việc với kiểu dữ liệu chuỗi.
3.1. Nối chuỗi kí tự: Toán tử +
Toán tử + được sử dụng để kết hợp nhiều chuỗi thành một chuỗi duy nhất. Mỗi phép cộng giữa các chuỗi sẽ tạo ra một chuỗi mới chứa nội dung của tất cả các chuỗi ban đầu. Trong trường hợp này, + thường được gọi là toán tử nối chuỗi.
city = "Ha Noi"
weather = "sunny"
sentence = "Today in " + city + " it is " + weather + "."
print(sentence)
# Kết quả: 'Today in Ha Noi it is sunny.'
Trong ví dụ trên, các chuỗi được ghép nối theo thứ tự từ trái sang phải. Python cho phép sử dụng đồng thời chuỗi lưu trong biến và chuỗi viết trực tiếp để tạo ra kết quả cuối cùng.
Toán tử nối chuỗi dạng rút gọn +=
Ngoài cách ghép chuỗi thông thường, Python còn hỗ trợ toán tử += để nối chuỗi theo cách ngắn gọn hơn.
url = "https://example.com"
url += "/login"
print(url)
# Kết quả: 'https://example.com/login'
Mặc dù cú pháp trông giống như đang thay đổi chuỗi ban đầu, nhưng thực tế Python tạo ra một chuỗi mới và gán lại cho biến, do chuỗi là kiểu dữ liệu không thể thay đổi trực tiếp.
3.2. Lặp chuỗi kí tự: Toán tử *
Toán tử * cho phép nhân một chuỗi với một số nguyên, từ đó tạo ra chuỗi mới bằng cách lặp lại nội dung ban đầu nhiều lần.
Cú pháp:
string * n
n * string
Ví dụ:
print(3 * "Go ")
# Kết quả: 'Go Go Go '
Nếu số lần lặp bằng 0 hoặc là số âm, kết quả sẽ là một chuỗi rỗng.
print("Hello" * 0)
# Kết quả: ''
print("Hello" * -2)
# Kết quả: ''
Ứng dụng của toán tử * trong định dạng dữ liệu
Toán tử lặp chuỗi thường được sử dụng để tạo các đường phân cách, đặc biệt khi hiển thị dữ liệu dạng bảng trên màn hình.
Ví dụ, với dữ liệu:
courses = [
["Python Basic", "120"],
["Data Analysis", "85"],
["Web Design", "60"],
]
Ta có thể tạo một hàm hiển thị bảng:
def print_courses(data, headers):
max_width = max(len(header) for header in headers)
print(" | ".join(header.ljust(max_width) for header in headers))
print("-|-".join("-" * max_width for _ in headers))
for row in data:
print(" | ".join(item.ljust(max_width) for item in row))
Khi gọi hàm:
headers = ["Course Name", "Students"]
print_courses(courses, headers)
Kết quả:
Course Name | Students
-------------|---------
Python Basic | 120
Data Analysis| 85
Web Design | 60
Trong ví dụ này, toán tử * được dùng để lặp lại ký tự -, tạo thành các đường phân cách có độ dài phù hợp với tiêu đề. Nhờ đó, bảng dữ liệu trở nên gọn gàng, cân đối và dễ theo dõi hơn, ngay cả khi độ dài nội dung thay đổi.
Toán tử lặp chuỗi rút gọn *=
Python cũng cung cấp dạng viết tắt của phép lặp chuỗi thông qua toán tử *=.
Ví dụ:
border = "="
border *= 8
print(border)
# Kết quả: '========'
3.3. Tìm chuỗi kí tự con: Toán tử in và not in
Hai toán tử in và not in được dùng để xác định sự xuất hiện của một chuỗi bên trong chuỗi khác. Kết quả của phép kiểm tra này là giá trị đúng hoặc sai (True/ False).
Toán tử in
Toán tử in cho biết chuỗi cần kiểm tra có tồn tại trong chuỗi gốc hay không.
print("cat" in "The cat is sleeping") # True
print("dog" in "The cat is sleeping") # False
Toán tử not in
print("x" not in "apple") # True
print("a" not in "apple") # False
Toán tử not in trả về True khi chuỗi không xuất hiện trong chuỗi được kiểm tra, và trả về False nếu chuỗi xuất hiện.
4. Các hàm có sẵn để xử lí chuỗi kí tự
Hàm dựng sẵn (Built-in function) là các hàm cốt lõi được xây dựng sẵn trong Python, có thể sử dụng trực tiếp trong các chương trình Python mà không cần phải cài đặt thêm bất kỳ thư viện nào từ bên ngoài. Các hàm dựng đặc biệt hữu ích khi người dùng cần xử lí các nhiệm vụ đơn giản và cần sự nhanh chóng.
Trong xử lí chuỗi, Python cũng cung cấp cho người dùng rất nhiều các hàm dựng sẵn hữu dụng. Sau đây là những hàm thường được sử dụng khi làm việc với chuỗi:
| Hàm | Mô tả |
|---|---|
len() |
Hàm trả về độ dài của chuỗi cần xử lý. |
str() và repr() |
Hàm chuyển một đối tượng (Object) thành chuỗi. |
format |
Hàm cho phép định dạng chuỗi theo mong muốn. |
ord() |
Hàm chuyển một ký tự thành một giá trị số. |
chr() |
Hàm chuyển một số thành một ký tự. |
Để mọi người hiểu rõ về các hàm dựng sẵn vừa được liệt kê, trong những phần tiếp theo các hàm trên sẽ được giới thiệu kĩ hơn và giới thiệu về cách sử dụng khi ta thực sự làm việc với chuỗi.
4.1. Hàm đếm số lượng phần tử: len()
Để bắt đầu tìm hiểu về hàm len(), ta hãy xem xét ví dụ sau:
password1 = 'iloveai'
password2 = 'AIVietNam'
def check_password(password):
if len(password) < 8 or len(password) > 25:
print('Password for registration is invalid')
else:
print('Registration successful')
check_password(password1)
# Kết quả: 'Password for registration is invalid'
check_password(password2)
# Kết quả: 'Registration successful'
Đây là phiên bản đơn giản của một chương trình kiểm tra tính hợp lệ của mật khẩu khi người dùng đăng ký các tài khoản mạng xã hội mà mọi người thường gặp dựa trên số lượng kí tự.
Từ ví dụ có thể thấy, khi làm việc với các chuỗi, việc xác định số lượng kí tự trong chuỗi là việc thường gặp. Và hàm len() là một công cụ rất hữu ích để hoàn thành việc này.
Khi truyền vào hàm len() một chuỗi bất kì, hàm sẽ trả về cho ta số lượng kí tự có trong chuỗi đó. Sau đây là một số ví dụ đơn giản về cách sử dụng hàm len().
len('AIOConquer') # Kết quả: 10
len('') # Kết quả: 0
Do cụm từ AIOConquer có tổng cộng 10 kí tự nên hàm len() sẽ trả về giá trị 10. Chú ý, khi ta truyền vào hàm len() một chuỗi rỗng, hàm sẽ trả về giá trị 0.
Ngoài ra, bên cạnh việc sử dụng cho việc xử lí, chuỗi hàm dựng sẵn len() còn có thể sử dụng cho các kiểu dữ liệu khác như: danh sách, bộ giá trị (tuple), từ điển (dictionary),...
4.2. Hàm chuyển đối tượng thành chuỗi kí tự: str() và repr()
Khi sử dụng các hàm được xây dựng cho việc xử lí chuỗi, chúng ta cần đảm bảo dữ liệu ta đang làm việc có kiểu dữ liệu là chuỗi. Nhưng trong thực tế, dữ liệu thường được lưu với đa dạng kiểu dữ liệu khác nhau bên cạnh kiểu dữ liệu chuỗi. Và với nhu cầu chuyển các kiểu dữ liệu về dạng chuỗi, Python đã cung cấp cho chúng ta hai hàm dựng sẵn là str() và repr().
Bắt đầu với hàm str(), khi truyền vào hàm str() một đối tượng, hàm sẽ trả về một chuỗi theo dạng thân thiện người dùng. Sau đây là một số ví dụ về cách dùng của hàm str():
str(26) # Kết quả: '26'
str([1, 2, 3]) # Kết quả: '[1, 2 ,3]'
str({"one": 1, "two": 2, "three": 3})
# Kết quả: '{"one": 1, "two": 2, "three": 3}'
Với hàm repr(), chức năng và cách sử dụng của hàm tương tự như hàm str(). Khi truyền vào hàm repr() một đối tượng, hàm sẽ trả về một chuỗi với dạng thân thiện với lập trình viên.
repr(26) # Kết quả: '26'
repr([1, 2, 3]) # Kết quả: '[1, 2 ,3]'
repr({"one": 1, "two": 2, "three": 3})
# Kết quả: '{"one": 1, "two": 2, "three": 3}'
Theo những ví dụ được liệt kê, hai hàm str() và repr() đều cho ra các kết quả tương tự nhau với đa số các kiểu dữ liệu có sẵn trong python.
Để phân biệt hàm str() và repr() mọi người hãy xem qua các ví dụ sau:
a = 'AIO'
str(a) # Kết quả: 'AIO'
repr(a) # Kết quả: "'AIO'"
import numpy as np
a = np.array([1, 2, 3])
str(a) # Kết quả: '[1, 2 ,3]'
repr(a) # Kết quả: 'array([1, 2 ,3])'
Trong ví dụ trên, hàm dựng sẵn repr() trả về toàn bộ thông tin về cách đối tượng được xây dựng, qua đó lập trình viên có thể xây dựng lại đối tượng thông qua thông tin được trả về. Trong khi đó, hàm str() không trả về thông tin nào liên quan tới cách xây dựng đối tượng mà chỉ trả về kiểu chuỗi để phù hợp hơn với người dùng.
4.3. Hàm định dạng chuỗi kí tự: format()
Hàm dựng sẵn format() được sử dụng để chuyển giá trị đầu vào thành chuỗi đã được định dạng theo mong muốn. Các loại định dạng được xác định thông qua các chỉ định định dạng (format specifier). Sau đây là các ví dụ về cách dùng hàm format() và một số các chỉ định định dạng thường dùng:
format(3.14159 , ".4f")
# Kết quả: '3.1416'
format(10, ".2f")
# Kết quả: '10.00'
format(1234567, ",")
# Kết quả: '1,234,567'
format(0.25, ".1%")
# Kết quả: '25.0%'
format(10, "b")
# Kết quả: '1010'
Trong các ví dụ trên, chỉ định định dạng ".4f" và ".2f" sẽ chuyển giá trị đầu vào thành số thực và làm tròn tới lần lượt 4 và 2 chữ số sau dấu phẩy.
Chỉ định định dạng "," sẽ phân tách giá trị đầu vào bằng dấu phẩy mỗi phần nghìn. Tiếp đến, chỉ định định dạng ".1%" sẽ chuyển giá trị đầu vào thành giá trị phần trăm. Cuối cùng, chỉ định định dạng "b" sẽ chuyển giá trị đầu vào sang dạng nhị phân.
4.4. Hàm tìm điểm mã (code point) của ký tự: ord() và chr()
Về bản chất, máy tính không hiểu những kí tự mà con người sử dụng, chúng chỉ lưu trữ và xử lí thông tin ở dạng số. Để lưu trữ các kí tự vào máy tính, mỗi kí tự cần được gắn với một số duy nhất, thường được gọi là điểm mã (code point).
Các bảng quy định mối liên hệ giữa các kí tự và điểm mã được chấp nhận rộng rãi hiện nay bao gồm ASCII và Unicode. Bảng mã ASCII chứa các ký tự Latin thường được sử dụng trong tiếng Anh do độ phổ biến của ngôn ngữ này. Tuy nhiên trên thực tế, có rất nhiều các kí tự khác bên cạnh các kí tự Latin. Bảng mã Unicode sẽ quy định phần lớn các kí tự đang được sử dụng trong các nội dung công nghệ số.
Hàm dựng sẵn ord() được xây dựng sẵn trong Python để chuyển các kí tự đầu vào thành các điểm mã. Lưu ý, các ký tự nằm trong bảng mã ASCII sẽ được hàm ord() chuyển thành điểm mã được quy định trong bảng ASCII. Với những ký tự còn lại, hàm ord() sẽ trả về giá trị được quy định trong bảng mã Unicode.
Cách sử dụng hàm ord() sẽ được giới thiệu thông qua các ví dụ sau:
ord("A")
# Kết quả: 65
ord("π")
# Kết quả: 960
ord("∞")
# Kết quả: 8734
ord("😀")
# Kết quả: 128512
Ngược lại với hàm ord(), hàm dựng sẵn chr() được sử dụng để chuyển các giá trị số thành ký tự tương ứng với điểm mã của chính nó.
chr(65)
# Kết quả: 'A'
chr(960)
# Kết quả: 'π'
chr(8734)
# Kết quả: '∞'
chr(128512)
# Kết quả: '😀'
5. Truy xuất chỉ mục và Cắt lát chuỗi kí tự
Truy xuất chỉ mục (indexing) và cắt lát (slicing) xác định cách Python cho phép bạn quan sát và trích xuất dữ liệu từ chuỗi. Chúng mang tính nền tảng không phải vì cú pháp phức tạp, mà vì chúng phản ánh những lựa chọn thiết kế cốt lõi của Python liên quan đến ngữ nghĩa của kiểu tuần tự (sequence semantics), tính bất biến (immutability) và xử lí lỗi (error handling). Việc hiểu các quy tắc nền tảng giúp bạn suy luận chính xác về hành vi của chuỗi, tránh lỗi biên và viết mã vừa an toàn vừa đúng phong cách Python.
5.1. Truy xuất chỉ mục
Định nghĩa và Tổng quan Quy tắc
Truy xuất chỉ mục là thao tác lấy ra đúng một kí tự từ chuỗi bằng cách sử dụng vị trí nguyên (integer position). Trong Python, chuỗi là một dãy kí tự có thứ tự và bất biến (ordered, immutable sequence); do đó, truy xuất chỉ mục sẽ chọn chính xác một phần tử trong dãy đó.
s = "Python"
s[0] # 'P'
Truy xuất chỉ mục là một thao tác truy cập nghiêm ngặt: hoặc thành công với một ký tự hợp lệ, hoặc thất bại ngay lập tức bằng một lỗi.
Truy xuất chỉ mục bắt đầu từ 0 (Zero-Based Indexing)
Python sử dụng truy xuất chỉ mục bắt đầu từ 0 (zero-based indexing), nghĩa là kí tự đầu tiên của chuỗi nằm tại chỉ mục 0.
s = "Python"
s[0] # 'P'
s[1] # 'y'
s[2] # 't'
s[3] # 'h'
s[4] # 'o'
s[5] # 'n'
Về mặt khái niệm, chỉ mục (index) biểu diễn một độ lệch (offset) tính từ điểm bắt đầu của dãy, chứ không phải là số thứ tự theo cách con người thường đánh số. Mô hình này được áp dụng nhất quán cho mọi kiểu dữ liệu dạng sequence trong Python, qua đó cho phép lập luận biên (boundary reasoning) một cách thống nhất và chính xác.
Truy xuất chỉ mục Dương và Âm (Cơ chế xử lý nội bộ)
Bên cạnh các chỉ mục không âm, Python còn hỗ trợ chỉ mục âm (negative indices), dùng để đếm từ cuối chuỗi.
s = "Python"
s[-1] # 'n'
Về mặt nội bộ, Python chuyển đổi chỉ mục âm theo quy tắc sau:
effective_index = len(s) + index
Ví dụ:
len("Python") # 6
"Python"[-1] -> 6 + (-1) = 5
Sau khi diễn giải theo cách này, Python áp dụng cùng một cơ chế kiểm tra giới hạn (bounds checking) như đối với chỉ mục không âm.
Vì vậy, chỉ mục âm không phải là một trường hợp đặc biệt trong quá trình truy xuất - nó chỉ là một cách viết khác để tham chiếu tới các vị trí từ cuối chuỗi.
Hành vi Lỗi và Tính Nghiêm ngặt
Truy xuất chỉ mục trong Python áp dụng các quy tắc biên nghiêm ngặt:
- Chỉ mục phải là một số nguyên (integer)
- Chỉ mục sau khi được phân giải (resolved index) phải thoả mãn điều kiện:
0 ≤ index < len(s) - Nếu không thoả mãn, Python sẽ phát sinh
IndexError
Python không tự động kẹp chỉ mục (clamp) và cũng không trả về giá trị đánh dấu (sentinel values).
Việc truy cập không hợp lệ được xem là lỗi lập trình, và lỗi được thiết kế để biểu hiện một cách rõ ràng.
s = "Python"
s[6] # IndexError
s[-7] # IndexError
Hệ quả của Tính Bất biến
Chuỗi trong Python là bất biến (immutable). Truy xuất chỉ mục cho phép bạn truy cập (access) một ký tự, nhưng không cho phép sửa đổi (modify) kí tự đó.
s = "Python"
s[0] = "y" # TypeError
Điều này phản ánh một sự phân biệt khái niệm mang tính then chốt:
- Truy cập dữ liệu (accessing data): đọc một giá trị đã tồn tại (được phép)
- Sửa đổi dữ liệu (modifying data): thay đổi đối tượng nền bên dưới (không được phép)
Thao tác truy xuất chỉ mục luôn trả về một đối tượng chuỗi mới có độ dài bằng 1, và không bao giờ cung cấp một mutable view (khung nhìn có thể thay đổi) trỏ trực tiếp vào chuỗi gốc.
Áp dụng
- Sử dụng chỉ mục âm khi suy luận hoặc truy cập dữ liệu từ cuối chuỗi
- Kiểm tra độ dài chuỗi khi truy xuất chỉ mục từ dữ liệu bên ngoài hoặc đầu vào do người dùng kiểm soát
- Giữ biểu thức truy xuất chỉ mục đơn giản và thể hiện rõ mục đích
- Chỉ sử dụng truy xuất chỉ mục khi thực sự cần đúng một kí tự duy nhất
5.2. Cắt chuỗi kí tự
Định nghĩa và Mục đích
Cắt lát (slicing) dùng để trích xuất một chuỗi con (subsequence) các kí tự từ một chuỗi. Khác với truy xuất chỉ mục, cắt lát hoạt động trên các khoảng (ranges) và có thể trả về không ký tự nào, một kí tự, hoặc nhiều kí tự.
s = "Python"
s[1:4] # 'yth'
Cắt lát luôn trả về một chuỗi mới và không bao giờ làm thay đổi (mutate) chuỗi gốc.
Cú pháp và Tham số:
Dạng đầy đủ của cú pháp cắt lát là:
s[start:stop:step]
start: chỉ mục nơi việc cắt lát bắt đầu (bao gồm – inclusive)stop: chỉ mục nơi việc cắt lát kết thúc (không bao gồm – exclusive)step: bước nhảy giữa các phần tử (stride), mặc định là1
Mỗi thành phần đều có thể được lược bỏ, nhưng mỗi thành phần đều tuân theo các quy tắc ngữ nghĩa cố định.
Ranh giới Bao gồm và Loại trừ
Cắt lát trong Python tuân theo một quy ước ranh giới có chủ đích:
startlà bao gồm (inclusive)stoplà loại trừ (exclusive)
s = "Python"
s[0:2] # 'Py'
Quy tắc này đảm bảo một bất biến (invariant) có thể dự đoán được:
len(s[start:stop]) == stop - start
Việc loại trừ stop không phải là tuỳ tiện - nó giúp đơn giản hoá việc kết hợp (composition), tránh chồng lấn (overlap) và khiến các lát cắt liền kề khớp với nhau một cách rõ ràng.
Quy tắc Chuẩn hoá Cắt lát
Trước khi thao tác cắt lát được thực thi, Python chuẩn hoá các ranh giới của lát cắt:
- Các giá trị bị thiếu sẽ được thay thế bằng giá trị mặc định
| Trường hợp | Giá trị mặc định |
|---|---|
start bị thiếu |
0 |
stop bị thiếu |
len(s) |
- Các chỉ mục vượt phạm vi sẽ được kẹp (clamp) về giới hạn hợp lệ
s = "Python"
s[:100] # 'Python'
s[-100:3] # 'Pyt'
Chính nhờ quá trình chuẩn hoá này, cắt lát không bao giờ phát sinh IndexError.
Một khoảng không hợp lệ hoặc rỗng sẽ đơn giản chỉ tạo ra một chuỗi rỗng, thay vì gây ra lỗi.
Các Mẫu Cắt lát Phổ biến
s = "Python"
s[:3] # từ đầu chuỗi: 'Pyt'
s[3:] # đến cuối chuỗi: 'hon'
s[:] # sao chép toàn bộ chuỗi: 'Python'
Những dạng này được ưu tiên sử dụng khi ý định (intent) quan trọng hơn việc chỉ rõ ranh giới số học một cách rõ ràng, giúp mã nguồn dễ đọc và dễ hiểu hơn.
Chỉ mục Âm và Cắt lát Ngược
Chỉ mục âm trong cắt lát hoạt động hoàn toàn giống như trong truy xuất chỉ mục đơn lẻ:
s = "Python"
s[-3:] # 'hon'
s[:-3] # 'Pyt'
Chúng cho phép bạn suy luận từ cuối chuỗi một cách tự nhiên, mà không cần phải tính toán độ dài chuỗi một cách thủ công.
Tham số step và Duyệt Ngược
Tham số step kiểm soát hướng duyệt (traversal direction) và khoảng cách giữa các phần tử (spacing).
s = "Python"
s[::2] # 'Pto'
Khi step là số âm, thứ tự duyệt sẽ bị đảo ngược:
s[::-1] # 'nohtyP'
Về mặt cơ chế:
- Việc duyệt bắt đầu từ cuối chuỗi
- Mỗi bước di chuyển lùi lại một vị trí
- Các kí tự được tích luỹ theo thứ tự ngược
Đây chính là lí do vì sao cú pháp [::-1] có thể đảo ngược chuỗi một cách chính xác và đúng chuẩn Python.
Áp dụng
- Sử dụng cắt lát (slicing) cho các khoảng (ranges), và truy xuất chỉ mục (indexing) cho một ký tự đơn lẻ
- Ưu tiên lược bỏ ranh giới (
[:],[:n],[n:]) để tăng khả năng đọc hiểu (readability) - Tận dụng cơ chế chuẩn hoá của slicing thay vì tự kiểm tra biên thủ công
- Sử dụng
stepmột cách có chủ đích, tránh làm không rõ ý định của đoạn mã
6. Nội suy và Định dạng chuỗi kí tự
6.1. Toán tử %
Trước phiên bản Python 3.6, toán tử % (modulo) và phương thức .format() là hai công cụ chính dùng để nội suy (interpolate) các giá trị, biến và biểu thức trong chuỗi kí tự. Trong phần này, ta sẽ khám phá cách sử dụng toán tử %.
Dưới đây là đoạn mã sử dụng toán tử modulo để nội suy một chuỗi kí tự:
vietnam = "Vietnam"
"Hello AI %s" % vietnam
# Kết quả: 'Hello AI Vietnam'
Có thể thấy toán từ modulo nhận vào hai toán hạng:
- Toán hạng bên trái là một chuỗi kí tự chứa một hoặc nhiều chỉ định chuyển đổi (ở ví dụ trên là chuỗi
%s) - Toán hạng bên phải là đối tượng (object) được nội suy vào chuối kí tự bên trái
Ở ví dụ trên, giá trị của biến vietnam được nội suy vào chỉ định chuyển đổi (conversion specifier) %s. Chuỗi %s gồm 2 thành phần, (1) % là ký tự xác định đây là một chỉ định chuyển đổi và (2) s là định dạng của đối tượng sau khi nội suy. Cụ thể kí tự s viết tắt cho string (chuỗi kí tự), ý nói đối tượng được thay thế vào chỉ định chuyển đối này phải ở dạng chuỗi kí tự.
Để hiểu thêm về chỉ định chuyển đổi, ta xét đoạn mã sau:
number = 123.456
"%s is a string" % number
# Kết quả: '123.456 is a string'
"%d is a decimal" % number
# Kết quả: '123 is a decimal'
"%f is a float" % number
# Kết quả: '123.456000 is a float'
"%.2f is a float" % number
# Kết quả: '123.46 is a float'
Có thể thấy, tuy cùng một biến number, nhưng khi thay đổi định dạng thì giá trị nội suy cũng thay đổi theo.
d(decimal) là định dạng số thập phân, ép kiểu của giá trị về dạng thập phân.f(float) ép kiểu của giá trị thành số thực với 6 chữ số sau dấu chấm động.- Khi thêm
.2phía trướcf, ta chuyển định dạng của đối tượng thành số thực được làm tròn 2 chữ số sau dấu chấm động.
Để có thể nội suy nhiều giá trị cùng một lúc, ta thay toán hạng bên phải từ một biến thành cấu trúc dữ liệu bộ giá trị (tuple). Thứ tự nội suy sẽ trùng với thứ tự của từng phần tử trong tuple.
hello = "Hello"
vietnam = "Vietnam"
"%s AI %s" % (hello, vietnam)
# Kết quả: 'Hello AI Vietnam'
Ngoài tuple, ta cũng có thể sử dụng cấu trúc dữ liệu từ điển (dictionary). Đối với kiểu dữ liệu từ điển, tứ thự nội suy xác định bằng cách thêm khóa (key) vào sau kí tự % và định dạng của chỉ định chuyển đổi.
hello = "Hello"
vietnam = "Vietnam"
"%(hello)s AI %(vietnam)s" % {"vietnam": vietnam, "hello": hello}
# Kết quả: 'Hello AI Vietnam'
Tuy toán tử % cho phép ta nội suy một cách đơn giản và nhanh chóng nhưng nó cũng có một vài hạn chế. Ví dụ như ta muốn nội suy một bộ giá trị (tuple) chứ không phải từng phần tử trong nó ta phải bao bộ giá trị đó trong một bộ giá trị khác.
ai = "AI"
vietnam = "Vietnam"
"Hello %s" % (ai, vietnam)
# TypeError: not all arguments converted during string formatting
"Hello %s" % ((ai, vietnam),)
# Kết quả: 'Hello ('AI', 'Vietnam')'
6.2. Phương thức .format()
Phương thức .format() là phiên bản cải tiến của toán tử % khi đã sửa chữa một vài vấn đề và hỗ trợ ngôn ngữ định dạng chuỗi thu nhỏ (string formatting mini-language). .format() sử dụng các cặp dấu ngoặc nhọn thay cho các chỉ định chuyển đổi.
hello = "Hello"
vietnam = "Vietnam"
"{} AI {}".format(hello, vietnam)
# Kết quả: 'Hello AI Vietnam'
Mặc định, thứ tự nội suy sẽ trùng với thứ tự các đối số của phương thức .format(). Tuy nhiên, ta có thể tùy chỉnh thứ tự nội suy bằng cách thêm các chỉ mục vào trong bộ dấu ngoặc nhọn.
hello = "Hello"
vietnam = "Vietnam"
"{1} AI {0}".format(hello, vietnam)
# Kết quả: 'Vietnam AI Hello'
Ngoài chỉ mục, ta cũng có thể dùng các đối số từ khóa (keyword arguments) để xác định thứ tự nội suy.
"{vietnam} AI {hello}".format(hello="Hello", vietnam="Vietnam")
# Kết quả: 'Vietnam AI Hello'
Bạn cũng có thể truyền các phần tử của cấu trúc dữ liệu từ điển vào .format bằng 2 dấu sao **.
dic = {hello="Hello", vietnam="Vietnam"}
"{vietnam} AI {hello}".format(**dic)
# Kết quả: 'Vietnam AI Hello'
Vì .format() không sử dụng các chỉ định chuyển đổi, để định dạng đối tượng được nội suy, .format() sử dụng các chỉ định định dạng (format specifiers). Gần giống như cú pháp của chỉ định chuyển đối, cú pháp chỉ định định dạng chỉ thay kí tự % thành :.
number = 123.456
"{:.2f} is a float".format(number)
# Kết quả: '123.46 is a float'
one = 1
two = 2
three = 3
"{2:d} + {1:d} = {0:d}".format(three, two, one)
# Kết quả: '1 + 2 = 3'
Ở đoạn mã trên, 2 trong 2:d là chỉ mục của đối số one, trong khi đó :d là định dạng số thập phân.
6.3. F-String
Sau phiên bản Python 3.6, f-string cung cấp cho người dùng thêm một cách nữa để có thể nội suy chuỗi kí tự. Cú pháp của f-string gần giống với .format() nhưng gọn hơn. Ta chỉ việc thêm chữ f thường hoặc hoa trước chuỗi kí tự và bọc các giá trị, biến, đối tượng và biểu thức trong cặp dấu ngoặc nhọn để nội suy.
hello = "Hello"
vietnam = "Vietnam"
f"{hello} AI {vietnam}"
# Kết quả: 'Hello AI Vietnam'
Bạn cũng có thể nội suy hầu hết cách biểu thức trong Python khi dùng f-string. Dưới đây là đoạn mã ví dụ thấy f-string nội suy được biểu thức tính toán, phương thức và cả biểu thức suy diễn danh sách (list comprehension):
f"1 + 2 = {1 + 2}"
# Kết quả: '1 + 2 = 3'
hello = "Hello"
vietnam = "Vietnam"
f"{hello} AI {vietnam.upper()}"
# Kết quả: 'Hello AI VIETNAM'
f"{[2**n for n in range(1, 5)]}"
# Kết quả: '[2, 4, 8, 16]'
Tương tự như .format(), f-string cũng sử dụng chỉ định định dạng để định dạng chuỗi nội suy.
number = 123.456
f"{number:.2f} is a float"
# Kết quả: '123.46 is a float'
Bên cạnh nội suy thông thường, f-string còn có một tính năng khá hay giúp cho quá trình gỡ lỗi (debug) của các lập trình viên trở nên dễ dàng hơn. Ví dụ, ta muốn kiểm tra nhanh giá trị của một biến, ta dùng cú pháp:
number = 123.456
print(f"{number = }")
# Kết quả: number = 123.456
number = 123.456
print(f"{number= }")
# Kết quả: number= 123.456
number = 123.456
print(f"{number=}")
# Kết quả: number=123.456
Đối với các phiên bản từ Python 3.12 trở về sau, f-string đã có một vài những nâng cấp.
Các phiên bản từ Python 3.11 trở xuống, khi muốn dùng dấu nháy đơn ' trong chuỗi ta phải bọc chuỗi kí tự đó bằng dấu nháy kép " và ngược lại. Nếu không Python sẽ báo lỗi. Nhưng đối với các phiên bản sau 3.12 bạn có thể sử dụng các dấu nháy tùy ý mà không cần phải bận tâm điều gì.
# Python 3.11
dic = {"hello": "Hello", "vietnam": "Vietnam"}
f"{dic['hello']} AI {dic['vietnam']}"
# Kết quả: 'Hello AI Vietnam'
f"{dic["hello"]} AI {dic["vietnam"]}"
# SyntaxError: f-string: unmatched '['
# Python 3.12
f"{dic["hello"]} AI {dic["vietnam"]}"
# Kết quả: 'Hello AI Vietnam'
Một nhược điểm nữa của f-string Python 3.11 trở xuống, là không thể sử dụng dấu \ trong các biểu thức f-string. Python 3.12 đã sửa điều đó:
# Python 3.11
words = ["Hello", "AI", "Vietnam"]
f"{'\n'.join(words)}"
# SyntaxError: f-string expression part cannot include a backslash
# Python 3.12
f"{'\n'.join(words)}"
# Kết quả: 'Hello\nAI\nVietnam'
F-String giờ đây cũng cho ta viết ghi chú nội dòng (inline comment) trong biểu thức nội suy.
# Python 3.12
vietnam = "Vietnam"
f"""Hello AI {
vietnam # This is a inline comment
}"""
# Kết quả: 'Hello AI Vietnam'
Ngoài ra thông báo lỗi của f-string phiên bản 3.12 chi tiết hơn. Khi nhập đoạn mã này:
f"{42 + }"
Các phiên bản dưới 3.12 sẽ báo lỗi như sau:
File "<stdin>", line 1
(42 + )
^
SyntaxError: f-string: invalid syntax
Còn đối với các phiên bản 3.12 trở về sau thì:
File "<stdin>", line 1
f"{42 + }"
^
SyntaxError: f-string: expecting '=', or '!', or ':', or '}'
6.4. So sánh toán tử %, phương thức .format() và f-string
Về hiệu suất, f-string chạy nhanh hơn toán tử % và phương thức format(). Bên cạnh đó, f-string cũng có rất nhiều tính năng hay như đã trình bày ở các mục trên. Tuy nhiên sẽ có những trường hợp % và format() sẽ là nhưng lựa chọn hợp lí hơn.
Nội suy cấu trúc từ điển là một trường hợp như vậy. Cú pháp của f-string trong trường hợp này sẽ trông rườm rà hơn khi dùng .format().
dic = {"hello": "Hello", "vietnam": "Vietnam"}
# f-string
f"{dic['hello']} AI {dic['vietnam']}"
# Kết quả: 'Hello AI Vietnam'
# .format()
"{hello} AI {vietnam}".format(**dic)
# Kết quả: 'Hello AI Vietnam'
Một trường hợp khác là cơ chế định trị trì hoãn (lazy evaluation).
F-String hay .format() sẽ thực hiện nội chuỗi kí tự ngay khi vừa khai báo, cơ chế này gọi là định trị tức thời (eager evaluation). Nội suy bằng toán tử % chỉ xảy ra khi ta sử dụng toán tử % chứ không phải lúc khai báo chuỗi kí tự, cơ chế này gọi là định trị trì hoãn. Để hiểu rõ ta xem ví dụ sau:
vietnam = 'Vietnam'
# f-string
a = f"Hello AI {vietnam}" # Ngay tại thời điểm này a = 'Hello AI Vietnam'
# .format()
b = "Hello AI {}".format(vietnam) # Ngay tại thời điểm này b = 'Hello AI Vietnam'
# %
c = "Hello AI %s" # Ngay tại thời điểm này c = 'Hello AI %s'
c = c % vietnam # Ngay tại thời điểm này c = 'Hello AI Vietnam'
Có thể thấy, nếu ta chưa muốn nội suy khi khai báo chuỗi kí tự ta sử dụng cách khai báo chuỗi có chỉ định chuyển đổi. Đến khi nào ta thật sự cần nội suy thì ta dùng toán tử modulo. Điều này hữu ích khi không phải chuỗi kí tự nào trong chương trình cũng cần phải nội suy.
Mô-đun logging cho phép lập trình viên chia lời nhắn theo các cấp (ví dụ: cấp gỡ lỗi (debug), cấp cảnh báo (warning), v.v.). Tùy thuộc vào nhu cầu sử dụng, các lập trình viên có thể cài đặt sẽ chỉ nhận lời nhắn ở cấp nào. Điều này giúp chương trình chỉ cần tính toán các lời nhắn cần thiết, tối ưu hiệu suất chương trình.
Giả sử, một chương trình có hàng trăm lời nhắn cấp gỡ lỗi, và mười lời nhắn cấp cảnh báo. Người lập trình viên hiện giờ chỉ quan tâm đến các lời nhắn cảnh báo. Nếu ta dùng f-string hay .format() để định nghĩa các lời nhắn, tất cả các lời nhắn đều được nội suy và gây tốn kém không đánh có. Nếu chúng ta sử dụng %, thì chỉ lời nhắn đúng cấp sẽ được nội suy.
import logging
msg = "This is a %s message!"
logging.warning(msg, "WARNING")
# Kết quả: WARNING:root:This is a WARNING message!
logging.debug(msg, "DEBUGGING")
# Hàm này vì không đúng cấp nên không thực hiện nội suy
7. Lời kết
Và đó là các công cụ đa dạng của Python để khởi tạo và xử lí chuỗi bằng các toán tử và các hàm tích hợp sẵn. Bài blog cũng hướng dẫn cách trích xuất các phần tử hoặc các phần của chuỗi hiện có bằng cách sử dụng toán tử lập chỉ mục (indexing) và cắt lát chuỗi (slicing). Đồng thời, giới thiệu và so sánh các phương pháp nội suy chuỗi.
8. Tài liệu tham khảo
Jablonski, J. (2024, November 30). Python's F-String for String Interpolation and Formatting. Real Python. https://realpython.com/python-f-strings
Ramos, L.P. (2024, December 22). Strings and Character Data in Python. Real Python. https://realpython.com/python-strings
Chưa có bình luận nào. Hãy là người đầu tiên!