Có thể bạn đã quen với câu lệnh with, một cách ngắn gọn để xử lý tài nguyên trong Python. Nhưng bạn đã bao giờ tự hỏi nó hoạt động như thế nào ở bên trong chưa? Sức mạnh của từ khóa with đến từ context manager.
Context manager là một mẫu thiết kế nền tảng trong Python, cung cấp cách tiếp cận có cấu trúc cho việc quản lý tài nguyên. Chúng đảm bảo tài nguyên được cấp phát, sử dụng đúng cách, và cuối cùng được giải phóng hoặc dọn dẹp, ngay cả khi có lỗi hoặc ngoại lệ.
Điều này đặc biệt quan trọng khi làm việc với các tài nguyên như tệp, kết nối mạng, hoặc handle cơ sở dữ liệu. Bằng cách dùng context manager, chúng ta có thể viết mã gọn gàng và đáng tin cậy hơn, không còn lo lắng chuyện quên đóng tệp hoặc nhả khóa.
Trong hướng dẫn này, chúng ta sẽ đi xa hơn các context manager mặc định của Python và học cách viết của riêng mình.
Tìm hiểu Context Manager trong Python
Bên dưới, context manager là các đối tượng định nghĩa hai phương thức đặc biệt: __enter__ và __exit__. Phương thức __enter__ được gọi khi bạn đi vào khối with, và giá trị trả về của nó được gán cho một biến trong khối đó. Ngược lại, phương thức __exit__ được gọi khi khối with kết thúc, bất kể kết thúc bình thường hay có ngoại lệ.
Cấu trúc này đảm bảo việc xử lý tài nguyên đúng đắn. Hãy xem một số context manager dựng sẵn trong Python để hiểu rõ hơn:
withopen('file.txt','r')asfile:
data =file.read()Được hỗ trợ bởi
Ở đây, open('file.txt', 'r') đóng vai trò là context manager. Khi bạn vào khối with, phương thức enter của đối tượng tệp được gọi, mở tệp và gán nó cho biến file. Sau đó bạn có thể dùng file.read() để truy cập nội dung tệp.
Tiếp theo, phương thức __exit__ của đối tượng tệp chắc chắn sẽ được gọi khi khối with kết thúc, kể cả khi có ngoại lệ. Phương thức này đảm nhiệm việc đóng tệp, đảm bảo bạn không để sót các handle tệp đang mở.
2. Khóa luồng
Vượt ra ngoài tệp, context manager cũng có thể được dùng để đồng bộ hóa luồng bằng threading.Lock():
import threading
lock = threading.Lock()with lock:# Critical sectionprint("This code is executed under lock protection.")Được hỗ trợ bởi
Ở đây, lock là một đối tượng threading.Lock, cũng là một context manager. Khi vào khối with, phương thức __enter__ sẽ chiếm khóa, đảm bảo chỉ một luồng có thể thực thi vùng tới hạn tại một thời điểm. Cuối cùng, phương thức __exit__ sẽ nhả khóa khi thoát khỏi khối with, cho phép các luồng khác tiếp tục.
3. Kết nối cơ sở dữ liệu
Tương tự, context manager có thể quản lý kết nối cơ sở dữ liệu:
import sqlite3
with sqlite3.connect('database.db')as connection:
cursor = connection.cursor()
cursor.execute("SELECT * FROM table")
rows = cursor.fetchall()Được hỗ trợ bởi
Lời gọi sqlite3.connect('database.db') là một context manager. Khi vào khối with, một kết nối tới cơ sở dữ liệu được thiết lập và gán cho connection. Sau đó bạn có thể dùng một cursor để tương tác với cơ sở dữ liệu. Phương thức __exit__ đảm bảo kết nối được đóng khi khối with kết thúc, ngăn ngừa rò rỉ tài nguyên.
4. Ổ cắm mạng
Context manager thậm chí có thể xử lý giao tiếp mạng:
import socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM)as s:
s.connect(('localhost',8080))
s.sendall(b'Hello, world')
data = s.recv(1024)Được hỗ trợ bởi
Ở đây, socket.socket(socket.AF_INET, socket.SOCK_STREAM) tạo một đối tượng socket đóng vai trò context manager. Phương thức __enter__ tạo socket, và trong khối with, bạn có thể kết nối, gửi và nhận dữ liệu. Phương thức __exit__ đảm bảo socket được đóng đúng cách khi xong việc.
5. Quét thư mục
Lần này, os.scandir('.') cung cấp cách lặp qua các mục trong thư mục:
with os.scandir('.')as entries:for entry in entries:print(entry.name)Được hỗ trợ bởi
os.scandir('.') đóng vai trò context manager ở đây. Phương thức __enter__ mở một phiên quét thư mục, và bạn có thể lặp qua các mục trong khối with. Phương thức __exit__ dọn dẹp phiên quét khi thoát.
Như bạn thấy, câu lệnh with, được hỗ trợ bởi context manager, đơn giản hóa quản lý tài nguyên bằng cách tự động cấp phát và thu hồi.
Viết context manager tùy chỉnh trong Python
Giờ hãy xem cách viết các manager tùy chỉnh của riêng bạn để kiểm soát chi tiết việc quản lý tài nguyên.
Có hai cách chính để viết context manager tùy chỉnh: dựa trên lớp (class-based) và dựa trên hàm (function-based).
Cách tiếp cận dựa trên lớp
Cách dựa trên lớp là phương pháp có cấu trúc và linh hoạt nhất để viết context manager. Bạn định nghĩa một lớp triển khai các phương thức đặc biệt __enter__ và __exit__.
Hãy xem ví dụ về lớp Timer đo thời gian thực thi:
import time
classTimer:def__enter__(self):
self.start_time = time.time()return self
def__exit__(self, exc_type, exc_value, traceback):
self.end_time = time.time()
elapsed_time = self.end_time - self.start_time
print(f"Elapsed time: {elapsed_time} seconds")# Example usageif __name__ =="__main__":with Timer()as timer:# Code block to measure the execution time
time.sleep(2)# Simulate some time-consuming operation
Elapsed time:2.002082347869873 seconds
Được hỗ trợ bởi
Ở đây, lớp Timer định nghĩa phương thức __enter__ để ghi nhận thời điểm bắt đầu khi bạn vào khối with. Nó trả về self để cho phép truy cập đối tượng trong khối. Phương thức __exit__ tính thời gian đã trôi qua khi thoát khỏi khối with và in ra.
Cách tiếp cận dựa trên hàm
Nếu bạn thích cách ngắn gọn hơn, có thể đánh đổi một chút tính linh hoạt, thì cách dựa trên hàm là lựa chọn tốt. Bạn dùng decorator contextmanager từ mô-đun contextlib để biến bất kỳ hàm nào thành một context manager.
Hãy xem cách hoạt động:
import time
from contextlib import contextmanager
@contextmanagerdeftimer():
start_time = time.time()yield
end_time = time.time()
elapsed_time = end_time - start_time
print(f"Elapsed time: {elapsed_time} seconds")# Example usageif __name__ =="__main__":with timer():
time.sleep(2)
Elapsed time:2.0020740032196045 seconds
Được hỗ trợ bởi
Decorator @contextmanager biến hàm timer thành một context manager. Bên trong hàm, start_time được ghi nhận, và câu lệnh yield tạm dừng thực thi, cho phép mã trong khối with chạy.
Cuối cùng, chức năng tương đương __exit__ được thực hiện bằng cách lấy thời điểm kết thúc và in thời gian đã trôi qua.
Về bản chất, bạn viết logic cho __enter__ trước từ khóa yield còn logic cho __exit__ nằm sau đó. Cả hai cách đều đạt cùng kết quả, lựa chọn tùy thuộc sở thích của bạn về cấu trúc và khả năng đọc.
Ví dụ thực tiễn về Context Manager
Giờ khi bạn đã hiểu cách viết context manager tùy chỉnh trong Python, hãy xem một số ví dụ thực tế nơi chúng cực kỳ hữu ích.
Quản lý thao tác với tệp
Thao tác với tệp là nhiệm vụ phổ biến trong nhiều ứng dụng. Dù đọc hay ghi tệp, quản lý đúng cách đảm bảo tính toàn vẹn dữ liệu và hiệu quả tài nguyên. Hãy xét một tình huống muốn ghi văn bản vào tệp bằng context manager tùy chỉnh.
Trong ví dụ này, chúng ta định nghĩa lớp FileManager để bắt chước hàm open trong Python nhằm xử lý thao tác tệp. Hãy phân tích từng phương thức:
__init__(self, filename, mode): Phương thức khởi tạo của lớp. Nó khởi tạo đối tượng FileManager với tên tệp và chế độ được cung cấp, chỉ định tệp sẽ mở và chế độ mở (ví dụ: đọc, ghi, nối).
__enter__(self): Phương thức được gọi khi vào khối with như thường lệ. Nó mở tệp theo filename ở chế độ mode. Đối tượng tệp mở được gán cho self.file và trả về để dùng trong khối with.
__exit__(self, exc_type, exc_value, traceback): Phương thức được gọi khi thoát khối with, bất kể có ngoại lệ trong khối hay không. Nó đảm bảo tệp được đóng đúng cách bằng cách gọi self.file.close() nếu self.file không phải None. Điều này ngăn rò rỉ tài nguyên và đảm bảo dọn dẹp.
Nếu bạn kiểm tra nội dung của example.txt sau khi chạy đoạn mã trên, bạn sẽ thấy văn bản đã được ghi:
Tất nhiên, ưu điểm của lớp FileManager là bạn có thể triển khai bất kỳ logic tùy chỉnh nào để làm việc với tệp. Chẳng hạn, bạn có thể chỉnh sửa để lớp lưu log những tệp nào được mở hoặc đóng vào một tệp văn bản.
Mô-đun datetime được nhập để làm việc với dấu thời gian.
Thêm phương thức log_action để ghi log thao tác với tệp.
Thêm tham số log_filename để chỉ định tên tệp log. Mặc định là 'file_log.txt'.
Tên tệp và hành động (mở hoặc đóng) được nối thêm vào tệp log bằng phương thức log_action trong các phương thức __enter__ và __exit__.
Bên trong phương thức log_action, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") được dùng để lấy dấu thời gian hiện tại theo định dạng "YYYY-MM-DD HH:MM:SS".
Dấu thời gian sau đó được đưa vào log cùng với tên tệp và hành động.
Giờ, nếu chúng ta in nội dung của file_log.txt, sẽ thấy các dòng log:
$ cat file_log.txt
2024-04-0312:37:02- Opened: example.txt
2024-04-0312:37:02- Closed: example.txt
Được hỗ trợ bởi
Một trường hợp sử dụng thực tế khác có thể triển khai như một context manager tùy chỉnh là công cụ quản lý cơ sở dữ liệu SQLite:
Một lần nữa, điều này bắt chước hàm sqlite3.connect, nhưng có thể cải tiến thêm bằng cách bổ sung logic quản lý cơ sở dữ liệu tùy chỉnh. Hãy kiểm tra manager hoạt động bằng vài hàm mẫu:
# Example usagedefcreate_table():with DatabaseConnection("example.db")as connection:
cursor = connection.cursor()
cursor.execute("""CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
username TEXT,
email TEXT)""")definsert_data(username, email):with DatabaseConnection("example.db")as connection:
cursor = connection.cursor()
cursor.execute("INSERT INTO users (username, email) VALUES (?, ?)",(username, email))
connection.commit()deffetch_data():with DatabaseConnection("example.db")as connection:
cursor = connection.cursor()
cursor.execute("SELECT * FROM users")return cursor.fetchall()if __name__ =="__main__":
create_table()
insert_data("john_doe","[email protected]")
insert_data("jane_doe","[email protected]")
users = fetch_data()print("Users in the database:")for user in users:print(user)
Users in the database:(1,'john_doe','[email protected]')(2,'jane_doe','[email protected]')(3,'john_doe','[email protected]')(4,'jane_doe','[email protected]')Được hỗ trợ bởi
Chúng ta có thể viết mã dễ bảo trì hơn nhiều bằng cách tách các thao tác cơ sở dữ liệu thành các hàm và dùng context manager tùy chỉnh vừa tạo.
Chủ đề nâng cao liên quan đến Context Manager trong Python
Trong phần này, chúng ta sẽ bàn về các chủ đề nâng cao liên quan đến context manager trong Python, gồm xử lý lỗi và lồng ghép context manager.
Xử lý lỗi
Xử lý lỗi là một khía cạnh thiết yếu của lập trình, và context manager trong Python cung cấp cách tiện lợi để quản lý lỗi trong các thao tác quản lý tài nguyên.
Trong ví dụ lớp FileManager, xử lý lỗi có thể được triển khai trong phương thức __exit__ để đảm bảo dọn dẹp đúng cách ngay cả khi có ngoại lệ. Ví dụ, nếu xảy ra lỗi khi ghi dữ liệu vào tệp (khi đang ở trong khối with), chúng ta vẫn muốn đảm bảo tệp được đóng để tránh rò rỉ tài nguyên. Ta có thể sửa phương thức __exit__ như sau:
Ở đây, trước tiên chúng ta đóng tệp bằng self.file.close() để đảm bảo dọn dẹp. Sau đó, kiểm tra xem có ngoại lệ xảy ra không (exc_type is not None). Nếu có, chúng ta in thông báo lỗi để cung cấp thông tin về sự cố.
Cụ thể:
exc_type là lớp ngoại lệ, như AttributeError và IndexError.
exc_value là thể hiện ngoại lệ kèm thông điệp.
traceback là toàn bộ vết ngăn xếp của lỗi.
Hãy chạy lớp FileManager với một đoạn mã cố ý sai:
with FileManager("example.txt","r")asfile:# Simulate a sample error
content =file.read()print(content[150])
An error occurred: string index out of rangeĐược hỗ trợ bởi
Vì vậy, để triển khai logic tùy chỉnh khi xử lý ngoại lệ trong các manager của bạn, đây là một quy trình mẫu bạn có thể làm theo:
def__exit__(self, exc_type, exc_value, traceback):# Perform cleanup...# Handle the exceptionifisinstance(exc_value, AttributeError):# Handle AttributeError hereprint(f"Exception message: {exc_value}")# Handle another exceptionelifisinstance(exc_value, AnotherError):...Được hỗ trợ bởi
Lồng ghép context manager
Lồng ghép context manager cho phép bạn quản lý đồng thời nhiều tài nguyên, mỗi cái có context manager riêng, trong một câu lệnh with duy nhất. Điều này đặc biệt hữu ích khi xử lý các thao tác phức tạp liên quan đến nhiều tài nguyên. Hãy xem cách chúng ta có thể lồng ghép context manager trong các ví dụ của mình.
Giả sử bạn muốn mở một tệp và thực hiện các thao tác cơ sở dữ liệu trong cùng một ngữ cảnh. Có thể, bạn muốn đưa nội dung của một tệp CSV vào cơ sở dữ liệu dưới dạng các hàng. Chúng ta có thể đạt được điều này bằng cách lồng ghép context manager FileManager và DatabaseConnection trong một câu lệnh with duy nhất:
if __name__ =="__main__":with FileManager("example.txt","w")asfile, DatabaseConnection("example.db")as connection:...Được hỗ trợ bởi
Trong ví dụ này, context manager FileManager và DatabaseConnection được lồng trong cùng một câu lệnh with. Điều này cho phép chúng ta mở tệp và thiết lập kết nối cơ sở dữ liệu đồng thời. Các thao tác ghi tệp và cơ sở dữ liệu tiếp theo được thực hiện trong ngữ cảnh kết hợp này.
Một điều cần lưu ý là tính dễ đọc của mã. Tôi khuyến nghị số lượng ngữ cảnh đồng thời không vượt quá hai.
Kết luận
Context manager trong Python cung cấp cách tiếp cận có cấu trúc để quản lý tài nguyên trong câu lệnh with, đảm bảo cấp phát và thu hồi tài nguyên đúng cách.
Xuyên suốt hướng dẫn này, chúng ta đã học những điều cơ bản về context manager, đi qua các ví dụ thực tiễn, và bàn về các chủ đề nâng cao như xử lý lỗi và lồng ghép. Bằng cách thành thạo viết context manager tùy chỉnh, bạn có thể viết mã gọn gàng, tin cậy hơn, cải thiện xử lý lỗi và quản lý tài nguyên hiệu quả.
Chủ đề viết context manager tùy chỉnh nằm trong danh mục “Python nâng cao”. Dưới đây là một số tài nguyên liên quan để nâng cao kỹ năng của bạn:
Tôi là người sáng tạo nội dung về khoa học dữ liệu với hơn 2 năm kinh nghiệm và là một trong những tài khoản có lượng người theo dõi lớn nhất trên Medium. Tôi thích viết các bài chuyên sâu về AI và ML với chút giọng điệu mỉa mai, vì bạn cũng phải làm gì đó để chúng bớt khô khan. Tôi đã xuất bản hơn 130 bài viết và một khóa học trên DataCamp, cùng với một khóa nữa đang được thực hiện. Nội dung của tôi đã được hơn 5 triệu lượt xem, trong đó 20 nghìn người đã trở thành người theo dõi trên cả Medium và LinkedIn.
Động lực
Có thể bạn đã quen với câu lệnh
with, một cách ngắn gọn để xử lý tài nguyên trong Python. Nhưng bạn đã bao giờ tự hỏi nó hoạt động như thế nào ở bên trong chưa? Sức mạnh của từ khóawithđến từ context manager.Context manager là một mẫu thiết kế nền tảng trong Python, cung cấp cách tiếp cận có cấu trúc cho việc quản lý tài nguyên. Chúng đảm bảo tài nguyên được cấp phát, sử dụng đúng cách, và cuối cùng được giải phóng hoặc dọn dẹp, ngay cả khi có lỗi hoặc ngoại lệ.
Điều này đặc biệt quan trọng khi làm việc với các tài nguyên như tệp, kết nối mạng, hoặc handle cơ sở dữ liệu. Bằng cách dùng context manager, chúng ta có thể viết mã gọn gàng và đáng tin cậy hơn, không còn lo lắng chuyện quên đóng tệp hoặc nhả khóa.
Trong hướng dẫn này, chúng ta sẽ đi xa hơn các context manager mặc định của Python và học cách viết của riêng mình.
Tìm hiểu Context Manager trong Python
Bên dưới, context manager là các đối tượng định nghĩa hai phương thức đặc biệt:
__enter__và__exit__. Phương thức__enter__được gọi khi bạn đi vào khối with, và giá trị trả về của nó được gán cho một biến trong khối đó. Ngược lại, phương thức__exit__được gọi khi khốiwithkết thúc, bất kể kết thúc bình thường hay có ngoại lệ.Cấu trúc này đảm bảo việc xử lý tài nguyên đúng đắn. Hãy xem một số context manager dựng sẵn trong Python để hiểu rõ hơn:
1. Quản lý tệp
Xem ví dụ kinh điển về việc mở một tệp:
Ở đây,
open('file.txt', 'r')đóng vai trò là context manager. Khi bạn vào khốiwith, phương thức enter của đối tượng tệp được gọi, mở tệp và gán nó cho biến file. Sau đó bạn có thể dùngfile.read()để truy cập nội dung tệp.Tiếp theo, phương thức
__exit__của đối tượng tệp chắc chắn sẽ được gọi khi khốiwithkết thúc, kể cả khi có ngoại lệ. Phương thức này đảm nhiệm việc đóng tệp, đảm bảo bạn không để sót các handle tệp đang mở.2. Khóa luồng
Vượt ra ngoài tệp, context manager cũng có thể được dùng để đồng bộ hóa luồng bằng
threading.Lock():Ở đây,
locklà một đối tượngthreading.Lock, cũng là một context manager. Khi vào khốiwith, phương thức__enter__sẽ chiếm khóa, đảm bảo chỉ một luồng có thể thực thi vùng tới hạn tại một thời điểm. Cuối cùng, phương thức__exit__sẽ nhả khóa khi thoát khỏi khốiwith, cho phép các luồng khác tiếp tục.3. Kết nối cơ sở dữ liệu
Tương tự, context manager có thể quản lý kết nối cơ sở dữ liệu:
Lời gọi
sqlite3.connect('database.db')là một context manager. Khi vào khốiwith, một kết nối tới cơ sở dữ liệu được thiết lập và gán choconnection. Sau đó bạn có thể dùng mộtcursorđể tương tác với cơ sở dữ liệu. Phương thức__exit__đảm bảo kết nối được đóng khi khốiwithkết thúc, ngăn ngừa rò rỉ tài nguyên.4. Ổ cắm mạng
Context manager thậm chí có thể xử lý giao tiếp mạng:
Ở đây,
socket.socket(socket.AF_INET, socket.SOCK_STREAM)tạo một đối tượng socket đóng vai trò context manager. Phương thức__enter__tạo socket, và trong khốiwith, bạn có thể kết nối, gửi và nhận dữ liệu. Phương thức__exit__đảm bảo socket được đóng đúng cách khi xong việc.5. Quét thư mục
Lần này,
os.scandir('.')cung cấp cách lặp qua các mục trong thư mục:os.scandir('.')đóng vai trò context manager ở đây. Phương thức__enter__mở một phiên quét thư mục, và bạn có thể lặp qua các mục trong khốiwith. Phương thức__exit__dọn dẹp phiên quét khi thoát.Như bạn thấy, câu lệnh
with, được hỗ trợ bởi context manager, đơn giản hóa quản lý tài nguyên bằng cách tự động cấp phát và thu hồi.Viết context manager tùy chỉnh trong Python
Giờ hãy xem cách viết các manager tùy chỉnh của riêng bạn để kiểm soát chi tiết việc quản lý tài nguyên.
Có hai cách chính để viết context manager tùy chỉnh: dựa trên lớp (class-based) và dựa trên hàm (function-based).
Cách tiếp cận dựa trên lớp
Cách dựa trên lớp là phương pháp có cấu trúc và linh hoạt nhất để viết context manager. Bạn định nghĩa một lớp triển khai các phương thức đặc biệt
__enter__và__exit__.Hãy xem ví dụ về lớp
Timerđo thời gian thực thi:Ở đây, lớp
Timerđịnh nghĩa phương thức__enter__để ghi nhận thời điểm bắt đầu khi bạn vào khối with. Nó trả vềselfđể cho phép truy cập đối tượng trong khối. Phương thức__exit__tính thời gian đã trôi qua khi thoát khỏi khốiwithvà in ra.Cách tiếp cận dựa trên hàm
Nếu bạn thích cách ngắn gọn hơn, có thể đánh đổi một chút tính linh hoạt, thì cách dựa trên hàm là lựa chọn tốt. Bạn dùng decorator
contextmanagertừ mô-đuncontextlibđể biến bất kỳ hàm nào thành một context manager.Hãy xem cách hoạt động:
Decorator
@contextmanagerbiến hàm timer thành một context manager. Bên trong hàm,start_timeđược ghi nhận, và câu lệnhyieldtạm dừng thực thi, cho phép mã trong khốiwithchạy.Cuối cùng, chức năng tương đương
__exit__được thực hiện bằng cách lấy thời điểm kết thúc và in thời gian đã trôi qua.Về bản chất, bạn viết logic cho
__enter__trước từ khóayieldcòn logic cho__exit__nằm sau đó. Cả hai cách đều đạt cùng kết quả, lựa chọn tùy thuộc sở thích của bạn về cấu trúc và khả năng đọc.Ví dụ thực tiễn về Context Manager
Giờ khi bạn đã hiểu cách viết context manager tùy chỉnh trong Python, hãy xem một số ví dụ thực tế nơi chúng cực kỳ hữu ích.
Quản lý thao tác với tệp
Thao tác với tệp là nhiệm vụ phổ biến trong nhiều ứng dụng. Dù đọc hay ghi tệp, quản lý đúng cách đảm bảo tính toàn vẹn dữ liệu và hiệu quả tài nguyên. Hãy xét một tình huống muốn ghi văn bản vào tệp bằng context manager tùy chỉnh.
Trong ví dụ này, chúng ta định nghĩa lớp
FileManagerđể bắt chước hàmopentrong Python nhằm xử lý thao tác tệp. Hãy phân tích từng phương thức:__init__(self, filename, mode): Phương thức khởi tạo của lớp. Nó khởi tạo đối tượngFileManagervới tên tệp và chế độ được cung cấp, chỉ định tệp sẽ mở và chế độ mở (ví dụ: đọc, ghi, nối).__enter__(self): Phương thức được gọi khi vào khốiwithnhư thường lệ. Nó mở tệp theofilenameở chế độmode. Đối tượng tệp mở được gán choself.filevà trả về để dùng trong khốiwith.__exit__(self, exc_type, exc_value, traceback): Phương thức được gọi khi thoát khối with, bất kể có ngoại lệ trong khối hay không. Nó đảm bảo tệp được đóng đúng cách bằng cách gọiself.file.close()nếu self.file không phảiNone. Điều này ngăn rò rỉ tài nguyên và đảm bảo dọn dẹp.Nếu bạn kiểm tra nội dung của
example.txtsau khi chạy đoạn mã trên, bạn sẽ thấy văn bản đã được ghi:Tất nhiên, ưu điểm của lớp
FileManagerlà bạn có thể triển khai bất kỳ logic tùy chỉnh nào để làm việc với tệp. Chẳng hạn, bạn có thể chỉnh sửa để lớp lưu log những tệp nào được mở hoặc đóng vào một tệp văn bản.Trong phiên bản đã chỉnh sửa này:
datetimeđược nhập để làm việc với dấu thời gian.log_actionđể ghi log thao tác với tệp.log_filenameđể chỉ định tên tệp log. Mặc định là'file_log.txt'.log_actiontrong các phương thức__enter__và__exit__.log_action,datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")được dùng để lấy dấu thời gian hiện tại theo định dạng"YYYY-MM-DD HH:MM:SS".Giờ, nếu chúng ta in nội dung của
file_log.txt, sẽ thấy các dòng log:Một trường hợp sử dụng thực tế khác có thể triển khai như một context manager tùy chỉnh là công cụ quản lý cơ sở dữ liệu SQLite:
Một lần nữa, điều này bắt chước hàm
sqlite3.connect, nhưng có thể cải tiến thêm bằng cách bổ sung logic quản lý cơ sở dữ liệu tùy chỉnh. Hãy kiểm tra manager hoạt động bằng vài hàm mẫu:Chúng ta có thể viết mã dễ bảo trì hơn nhiều bằng cách tách các thao tác cơ sở dữ liệu thành các hàm và dùng context manager tùy chỉnh vừa tạo.
Chủ đề nâng cao liên quan đến Context Manager trong Python
Trong phần này, chúng ta sẽ bàn về các chủ đề nâng cao liên quan đến context manager trong Python, gồm xử lý lỗi và lồng ghép context manager.
Xử lý lỗi
Xử lý lỗi là một khía cạnh thiết yếu của lập trình, và context manager trong Python cung cấp cách tiện lợi để quản lý lỗi trong các thao tác quản lý tài nguyên.
Trong ví dụ lớp FileManager, xử lý lỗi có thể được triển khai trong phương thức
__exit__để đảm bảo dọn dẹp đúng cách ngay cả khi có ngoại lệ. Ví dụ, nếu xảy ra lỗi khi ghi dữ liệu vào tệp (khi đang ở trong khốiwith), chúng ta vẫn muốn đảm bảo tệp được đóng để tránh rò rỉ tài nguyên. Ta có thể sửa phương thức__exit__như sau:Ở đây, trước tiên chúng ta đóng tệp bằng
self.file.close()để đảm bảo dọn dẹp. Sau đó, kiểm tra xem có ngoại lệ xảy ra không(exc_type is not None). Nếu có, chúng ta in thông báo lỗi để cung cấp thông tin về sự cố.Cụ thể:
exc_typelà lớp ngoại lệ, nhưAttributeErrorvàIndexError.exc_valuelà thể hiện ngoại lệ kèm thông điệp.tracebacklà toàn bộ vết ngăn xếp của lỗi.Hãy chạy lớp
FileManagervới một đoạn mã cố ý sai:Vì vậy, để triển khai logic tùy chỉnh khi xử lý ngoại lệ trong các manager của bạn, đây là một quy trình mẫu bạn có thể làm theo:
Lồng ghép context manager
Lồng ghép context manager cho phép bạn quản lý đồng thời nhiều tài nguyên, mỗi cái có context manager riêng, trong một câu lệnh
withduy nhất. Điều này đặc biệt hữu ích khi xử lý các thao tác phức tạp liên quan đến nhiều tài nguyên. Hãy xem cách chúng ta có thể lồng ghép context manager trong các ví dụ của mình.Giả sử bạn muốn mở một tệp và thực hiện các thao tác cơ sở dữ liệu trong cùng một ngữ cảnh. Có thể, bạn muốn đưa nội dung của một tệp CSV vào cơ sở dữ liệu dưới dạng các hàng. Chúng ta có thể đạt được điều này bằng cách lồng ghép context manager
FileManagervàDatabaseConnectiontrong một câu lệnhwithduy nhất:Trong ví dụ này, context manager FileManager và DatabaseConnection được lồng trong cùng một câu lệnh
with. Điều này cho phép chúng ta mở tệp và thiết lập kết nối cơ sở dữ liệu đồng thời. Các thao tác ghi tệp và cơ sở dữ liệu tiếp theo được thực hiện trong ngữ cảnh kết hợp này.Một điều cần lưu ý là tính dễ đọc của mã. Tôi khuyến nghị số lượng ngữ cảnh đồng thời không vượt quá hai.
Kết luận
Context manager trong Python cung cấp cách tiếp cận có cấu trúc để quản lý tài nguyên trong câu lệnh
with, đảm bảo cấp phát và thu hồi tài nguyên đúng cách.Xuyên suốt hướng dẫn này, chúng ta đã học những điều cơ bản về context manager, đi qua các ví dụ thực tiễn, và bàn về các chủ đề nâng cao như xử lý lỗi và lồng ghép. Bằng cách thành thạo viết context manager tùy chỉnh, bạn có thể viết mã gọn gàng, tin cậy hơn, cải thiện xử lý lỗi và quản lý tài nguyên hiệu quả.
Chủ đề viết context manager tùy chỉnh nằm trong danh mục “Python nâng cao”. Dưới đây là một số tài nguyên liên quan để nâng cao kỹ năng của bạn: