Lời mở đầu
Python là một ngôn ngữ lập trình mạnh mẽ với cú pháp dễ hiểu cho phép bạn có thể tự học mà không cần nhiều kiến thức nền tảng về khoa học máy tính. Trong quá trình học, có thể bạn sẽ mắc phải nhiều sai lầm do thiếu hiểu biết về các concept. Học cách sửa chữa những sai lầm này sẽ tăng cường hiểu biết của bạn về các kiến thức cơ bản cũng như các kĩ năng lập trình.
Bài viết này sẽ tổng hợp một số lỗi thường gặp trong Python mà nhiều người hay mắc phải khi mới bắt đầu học và cách sửa chữa, phòng tránh.
1. Tải lại các module sau khi sửa đổi
Bạn đã bao giờ tốn hàng tiếng đồng hồ để gỡ lỗi và sửa chữa một trục trặc và cuối cùng bạn nhận ra là mình không gỡ lỗi trên đoạn mã nguồn đã được sửa đổi ? Điều này thường xảy ra đối với những người mới học khi họ không nhận ra rằng cả module chỉ được tải lên bộ nhớ một lần khi lệnh import được thực thi. Vậy nếu bạn sửa code trong một module riêng và and import vào code hiện tại của bạn, bạn sẽ phải tải lại module để phản chiếu các thay đổi mới nhất.
Để tải lại một module, bạn sẽ cần phải dùng hàm reload từ importlib module:
1 from importlib import reload
2
3 # some module which you have made changes
4 import externallib
5
6 reload(externallib)
2. Đặt tên xung đột giữa biến toàn cục và biến cục bộ
Tưởng tượng rằng bạn định nghĩa một biến toàn cục tên là app_config, và bạn muốn sử dụng nó trong hàm init_config bên dưới:
1 app_config = "app.ini"
2
3 def init_config():
4 app_config = app_config or "default.ini"
5 print(app_config)
Bạn có thể đang chờ in ra “app.ini” vì nó đã được định nghĩa trên toàn cục, nhưng ngạc nhiên thay, bạn lại gặp phải ngoại lệ “UnboundLocalError” bởi biến app_config đã được tham chiếu trước khi gán. Nếu bạn diễn giải lệnh gán và in biến ra, bạn sẽ thấy giá trị được in ra chính xác. Vậy thì điều gì đang xảy ra ở đây ?
Ngoại lệ trên xảy ra bởi Python cố tạo ra một biến trong local scope bất cứ khi nào có một biểu thức gán, và khi biến cục bộ và biến toàn cục có cùng tên, biến toàn cục sẽ bị che đi trong local scope. Và vì vậy Python sẽ báo lỗi rằng biến cục bộ app_config đang được dùng trước khi nó được khởi tạo.
Để giải quyết đặt tên xung đột, bạn phải sử dụng các cái tên khác nhau cho biến toàn cục và biến cục bộ để tránh nhầm lẫn, ví dụ :
1 app_config = "app.ini"
2
3 def init_config():
4 config = app_config or "default.ini"
5 print(config)
3. Kiểm tra các giá trị Falsy
Kiểm tra true hoặc false của một biến trong lệnh if hoặc đôi khi có thể sai sót. Những người mới học Python thường lẫn lộn giữa giá trị None và các giá trị falsy khác và cuối cùng là viết ra những đoạn code lỗi. Ví dụ: giả sử bạn muốn kiểm tra xem nếu price không phải là None và dưới 5, thì chương trình kích hoạt một vài cảnh báo selling :
1 def selling_alert(price):
2 if price and price < 5:
3 print("selling signal!!!")
Mọi thứ trông có vẻ ổn, nhưng khi bạn kiểm tra với price = 0, bạn sẽ không có bất kì cảnh báo nào :
1 selling_alert(0)
2 # Nothing has been printed out
Điều này là do cả None và 0 đều được Python đánh giá là False, do vậy lệnh in sẽ bị bỏ qua mặc dù price < 5 là true.
Trong python, các đối tượng dãy trống chẳng hạn như “” (empty string), list, set, dict, tuple v….v… đều được đánh giá là False, và cả số 0 trong bất kì định dạng số học nào như 0 và 0.0. Vậy để tránh gặp phải trục trặc Bạn sẽ phải làm rõ ràng khi nào logic của bạn cần phải phân biệt giữa None và các giá trị False khac và phải chia logic ra nếu cần thiết, ví dụ:
1 if price is None:
2 print("invalid input")
3 elif price < 5:
4 print("selling signal!!!")
4. Giá trị mặc định và Liên kết biến
Giá trị mặc định có thể được sử dụng khi bạn muốn làm cho tham số hàm của bạn là tùy chọn nhưng vẫn thay đổi linh hoạt. Hãy tưởng tượng bạn cần thực thi một hàm logging với một tham số event_time, bạn muốn đặt cho nó một giá trị mặc định như một dấu thời gian khi nó chưa được đặt giá trị. Bạn có thể vui vẻ viết một vài dòng code như dưới đây:
1 from datetime import datetime
2
3 def log_event_time(event, event_time=datetime.now()):
4 print(f"log this event - {event} at {event_time}")
Và bạn sẽ cho rằng miễn là event_time không được cung cấp khi gọi hàm log_event_time, nó sẽ ghi lại một sự kiện với dấu thời gian khi hàm được gọi lên. Nhưng khi bạn kiểm tra lại:
1 log_event_time("check-in")
2
3 # log this event - check-in at 2021-02-21 14:00:56.046938
4
5 log_event_time("check-out")
6
7 # log this event - check-out at 2021-02-21 14:00:56.046938
Bạn sẽ thấy các sự kiện được ghi lại với cùng một dấu thời gian. Vậy tại sao giá trị mặc định cho event_time lại không hoạt động ?
Để trả lời cho câu hỏi này thì bạn phải biết rằng, liên kết biến diễn ra trong quá trình định nghĩa hàm. Với ví dụ trên, giá trị mặc định của event_time đã được gán khi hàm bắt đầu được định nghĩa. Và cùng một giá trị đó sẽ được sử dụng mỗi khi gọi hàm.
Để giải quyết, bạn phải gán None là giá trị mặc định và kiểm tra để ghi đè event_time trong gọi hàm khi nó là None. Ví dụ như sau:
1 def log_event_time(event, event_time=None):
2 event_time = event_time or datetime.now()
3 print(f"log this event - {event} at {event_time}")
Lỗi liên kết biến tương tự có thể xảy ra khi bạn thực thi các hàm lambda.
5. Giá trị mặc định cho các Đối tượng biến đổi
Một lỗi sai khác mà những người mới học Python thường mắc phải là đặt giá trị mặc định cho một tham số hàm có thể biến đổi. Ví dụ như tham số user_list trong hàm add_white_list dưới đây :
1 def add_white_list(user, user_list=[]):
2 user_list.append(user)
3 return user_list
Bạn có thể cho rằng khi user_list chưa được đưa ra, một danh sách trống sẽ được tạo, sau đó một người dùng mới sẽ được thêm vào danh sách này và được trả về. Nó sẽ hoạt động như dưới đây:
1 my_list = add_white_list('Jack')
2
3 # ['Jack']
4
5 my_list = add_white_list('Jill', my_list)
6
7 #['Jack', 'Jill']
Nhưng khi bạn muốn bắt đầu lại với một danh sách trống, thì bạn lại thấy kết quả như sau:
1 my_new_list = add_white_list('Joe')
2 # ['Jack', 'Jill', 'Joe']
Từ ví dụ về liên kết biến trước đó, chúng ta biết rằng giá trị mặc định cho user_list được tạo ra chỉ một lần trong thời gian định nghĩa hàm Và bởi list là có thể biến đổi, các thay đổi đối với đối tượng list sẽ được tham chiếu bởi các lần gọi hàm kế tiếp.
Để giải quyết vấn đề, chúng ta sẽ gán None là giá trị mặc định cho biến user_list và sử dụng một biến cục bộ để tạo danh sách mới khi user_list không được đưa ra khi gọi. Ví dụ.:
1 def add_white_list(user, user_list=None):
2 if user_list is None:
3 user_list = []
4 user_list.append(user)
5 return user_list
Ai đó có thể nhầm lẫn rằng datetime.now() sẽ tạo ra một class instance của Python, và nó cũng có thể biến đổi. Nếu bạn kiểm tra tài liệu Python bạn sẽ thấy sự thực thi của datetime thực ra là bất biến.
6. Hiểu nhầm các hàm được tích hợp sẵn của Python
Python có rất nhiều hàm mạnh mẽ được tích hợp sẵn functions mà một số hàm có vẻ giống nhau về tên gọi, và nếu bạn không dành thời gian đọc tài liệu, kết quả là bạn có thể dùng các hàm sai cách.
Ví dụ, bạn biết hàm sorted function được tích hợp sẵn hay hàm list sort đều có thể được sử đụng để sắp xếp đối tượng dãy. Nhưng thỉnh thoảng bạn có thể mắc phải lỗi dưới đây:
1 random_ints = [80, 53, 7, 92, 30, 31, 42, 10, 42, 18]
2
3 # The sorting is done in-place, and function returns None
4 even_integers_first = random_ints.sort(key=lambda x: x%2)
5
6 # Sorting is not done in-place, function returns a new list
7 sorted(random_ints)
Tương tự với hàm reverse và reversed:
1 # The reversing is done in-place, and function returns None
2 random_ints = random_ints.reverse()
3 # reversing is not done in-place, function returns a new generator
4 reversed(random_ints)
Và với hàm list append và extend:
1 crypto = ["BTC", "ETH"]
2
3 # the new list will be added as 1 element to crypto list
4 crypto.append(["XRP", "BNB"])
5
6 print(crypto)
7 #['BTC', 'ETH', ['XRP', 'BNB']]
8
9 # the new list will be flattened when adding to crypto list
10 crypto.extend(["UNI"])
11
12 print(crypto)
13 # ['BTC', 'ETH', ['XRP', 'BNB'], 'UNI']
7. Chỉnh sửa các nguyên tố trong khi quá trình lặp
Trong quá trình lặp một đối tượng dãy (sequence), bạn có thể muốn lọc ra một số nguyên tố dựa trên các điều kiện cụ thể.
Ví dụ, nếu bạn muốn lặp lại danh sách các số nguyên dưới đây và loại bỏ bất kì nguyên tố nào dưới 5. Bạn sẽ phải viết những dòng code sau:
1 a = [1, 2, 3, 4, 5, 6, 2]
2
3 for b in a:
4 if b < 5:
5 a.remove(b)
Nhưng khi kiểm tra đầu ra của danh sách a, bạn sẽ thấy kết quả không giống như dự đoán:
1 print(a)
2 # [4, 5, 6, 2]
Đó là bởi vì câu lệnh sẽ đánh giá biểu thức và tạo ra một trình phát (generator) để lặp các nguyên tố. Bởi chúng ta xóa bỏ các nguyên tố từ danh sách gốc, nó cũng thay đổi trạng thái của trình phát, và gây ra các kết quả khó đoán trước. Để giải quyết vấn đề, bạn có thể dùng list comprehension như ở dưới đây nếu các điều kiện lọc của bạ không phức tạp:
1 [b for b in a if b >= 5]
Hay nếu muốn, bạn có thể dùng filterfalse cùng với hàm lambda:
1 from itertools import filterfalse
2 list(filterfalse(lambda x: x < 5, a))
8. Lặp lại một Trình phát đã cạn kiệt (Exhausted Generator)
Nhiều người học Python bắt đầu viết code mà không hiểu về sự khác nhau giữa trình phát (generator) và trình lặp (iterator). Điều này sẽ gây ra lỗi lặp lại một trình phát đã cạn kiệt. Ví dụ như trình phát dưới đây, bạn có thể in ra giá trị trong một danh sách:
1 some_gen = (i for i in range(10))
2 print(list(some_gen))
3 # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Và đôi khi bạn quên mất rằng mình đã lặp trình phát một lần trước đó, và khi bạn cố thực thi câu lệnh dưới đây:
1 for x in some_gen:
2 print(x)
Bạn sẽ không thấy bất kì cái gì được in ra.
Để giải quyết vấn đề, trước tiên, bạn sẽ phải lưu kết quả vào một danh sách nếu bạn không xử lý quá nhiều dữ liệu, hoặc bạn có thể sử dụng hàm tee từ itertools module để tạo ra nhiều bản sao của trình phát để có thể lặp nhiều lần:
1 from itertools import tee
2
3 # create 3 copies of generators from the original iterable
4 x, y, z = tee(some_gen, n=3)
Kết luận
Trong bài viết này, chúng ta đã điểm qua một số lỗi mà bạn có thể gặp phải khi bắt đầu viết code với Python. Và chắc chắn một điều là bạn sẽ mắc phải nhiều lỗi hơn nếu bạn bắt tay vào code mà không hiểu về các kiến thức cơ bản. Nhưng như người ta hay nói: “Thất bại là mẹ thành công”, cuối cùng bạn sẽ đạt được mục tiêu của bản thân sau khi đã rút kinh nghiệm từ tất cả sai lầm và vượt qua mọi trở ngại.