Lỗi và ngoại lệ trong Python

Table of Content

Lỗi hoặc sai sót trong một chương trình được gọi chung là lỗi. Chúng hầu như là lỗi do lập trình viên gây ra. Quá trình tìm kiếm và loại bỏ lỗi gọi là gỡ lỗi (debugging). Lỗi có thể chia thành 3 nhóm chính:

  1. Lỗi cú pháp
  2. Lỗi thực thi
  3. Lỗi lôgic.

Lỗi cú pháp

Python sẽ tìm thấy những loại lỗi này khi nó cố gắng phân tích cú pháp chương trình của bạn và thoát ra với một thông báo lỗi mà không chạy bất chứ thứ gì. Lỗi cú pháp là những lỗi trong việc sử dụng ngôn ngữ Python, tương tự như lỗi chính tả hoặc ngữ pháp trong một ngôn ngữ như tiếng Anh, ví dụ câu would you some tea? không có nghĩa vì nó thiếu động từ.

Các lỗi cú pháp Python phổ biến bao gồm:

  • bỏ một từ khóa
  • đặt một từ khóa sai chỗ
  • bỏ đi một ký hiệu, chẳng hạn dấu hai chấm, dấu phảy hoặc dấu ngoặc
  • viết sai chính tả một từ khóa
  • thụt lề sai
  • khối trống

Bạn không thể để trống hoàn toàn một khối, chẳng hạn như phần thân của if hoặc của một hàm. Nếu muốn tạo một khối không làm gì cả, bạn có thể sử dụng pass bên trong khối.

Python sẽ cố gắng hết sức để cho bạn biết lỗi nằm ở đâu, nhưng đôi khi thông báo của nó có thể gây hiểu nhầm. Ví dụ như bạn quên thoát dấu ngoặc kép bên trong chuỗi, bạn có thể gặp lỗi cú pháp liên quan đến một vị trí phía sau trong mã, mặc dù nó không phải là nguồn gốc thực sự của vấn đề. Nếu bạn không thể thấy bất kỳ điều gì sai trên dòng được chỉ định trong thông báo lỗi, hãy thử quay lại vài dòng trước đó. Khi bạn lập trình nhiều hơn, bạn sẽ xác định và sửa lỗi tốt hơn.

Dưới đây là một số ví dụ về lỗi cú pháp trong Python:

myfunction(x, y):
    return x + y
else:
    print("Hello!")

if mark >= 50:
    print("You passed!")

if arriving:
    print("Hi!")
else:
    print("Bye!")

if flag:
print("Flag is set!")

Lỗi thực thi

Nếu một chương trình đúng về mặt ngữ pháp – tức là không có lỗi cú pháp – thì nó sẽ được chạy bởi trình thông dịch Python. Tuy nhiên, chương trình có thể thoát đột ngột khi thực thi nếu nó gặp lỗi thực thi – một vấn đề không được phát hiện khi chương trình được phân tích cú pháp, nhưng chỉ tiết lộ khi một dòng cụ thể được thực thi. Khi một chương trình bị tạm dừng vì lỗi thực thi, chúng ta nói rằng nó đã bị lỗi.

Một số ví dụ về lỗi thực thi trong Python:

  • chia cho số không
  • thực hiện phép toán trên dữ liệu không tương thích
  • sự dụng một định danh chưa được định nghĩa
  • truy cập phần tử của danh sách, khóa của từ điển hoặc thuộc tính của đối tượng không tồn tại.
  • cố gắng truy cập một file không tồn tại.

Lỗi thực thi thường xảy ra nếu bạn không xem xét tất cả các giá trị mà một biến có thể chứa, đặc biệt khi bạn xử lý dữ liệu nhập của người dùng. Bạn nên luôn cố gắng thêm các kiểm tra vào mã của mình để đảm bảo rằng nó có thử xử lý các trường hợp đầu vào xấu một cách duyên dáng. Chúng ta sẽ xem xét vấn đề chi tiết hơn phần xử lý ngoại lệ.

Lỗi lôgic

Lỗi lôgic là khó sửa nhất. Chúng xảy ra khi chương trình chạy mà không bị treo, nhưng tạo ra kết quả không chính xác. Lỗi là do sai sót của thuật toán chương trình. Bạn sẽ không nhận được thông báo lỗi vì không có lỗi cú pháp hoặc lỗi thực thi (ngoại lệ) nào xảy ra. Bạn sẽ phải tự mình tìm ra vấn đề bằng cách xem xét tất cả các phần có liên quan của mã.

Xử lý ngoại lệ

Nếu chúng ta biết rằng một phần cụ thể trong chương trình của chúng ta có khả năng gây ra lỗi thực thi, chúng ta có thể cho Python biết phải làm gì nếu điều đó xảy ra. Thay vì để lỗi làm hỏng chương trình của chúng ta, chúng ta có thể chặn nó, hãy làm gì đó đối với nó và cho phép chương trình tiếp tục chạy.

Tất các lỗi thực thi mà chúng ta gặp phải được gọi là ngoại lệ. Python có những đối tượng biểu diễn các loại ngoại lệ xảy ra trong trương chính, chúng là những đối tượng của của các lớp con của lớp Exception.

Câu lệnh tryexcept

Để xử lý các ngoại lệ, chúng ta sử dụng khối try-except:

try:
    age = int(input("Please enter your age: "))
    print("I see that  you are %d years old." % age)
except ValueError:
    print("Hey, that wasn't a number!")

Python sẽ chạy các lệnh trong khối try. Nếu có ngoại lệ thuộc lớp ValueError xảy ra tại bất cứ thời điểm nào khi nó đang thực thi, luồng điều khiển sẽ ngay lập tức chuyển đến khối except và các lệnh còn lại trong khối try bị bỏ qua.

Trong ví dụ này, chúng ta biết rằng có lỗi có thể xảy ra khi cố gắng chuyển đổi đầu vào của người dùng thành một số nguyên. Nếu chuỗi đầu vào không phải là một số, dòng này sẽ kích hoạt một lỗi ValueError – đó là lý do tại sao chúng ta chỉ định nó là loại lỗi mà chúng ta sẽ xử lý.

Chúng ta có thể chỉ định một kiểu ngoại lệ chung chung hơn – hoặc thậm chí chỉ định tất cả ngoại lệ, điều này khiến except khớp với bất kỳ ngoại lệ nào – nhưng đó là sẽ một ý tưởng tồi. Điều gì sẽ xảy ra nếu chúng ta gặp một lỗi hoàn toàn khác mà chúng ta không dự đoán trước? Nó cũng sẽ được xử lý và chúng ta không nhận thấy bất cứ điều gì bất thường đang xảy ra. Chúng ta muốn phản ứng theo những cách khác nhau đối với các loại lỗi khác nhau. Chúng ta nên luôn thử chọn các loại lỗi cụ thể thay vì chung chung cho các mệnh đề except của mình.

Một mệnh đề except có thể xử lý được nhiều loại lỗi: chúng ta có thể cung cấp nhiều loại ngoại lệ thay vì một loại:

try:
    dividend = int(input("Please enter the dividend: "))
    divisor = int(input("Please enter the divisor: "))
    print("%d / %d = %f" % (dividend, divisor, dividend/divisor))
except (ValueError, ZeroDivisionError):
    print("Oops, something went wrong!")

Chú ý rằng trong ví dụ trên nếu một ngoại lệ ValueError xảy ra, chúng ta sẽ không biết liệu đó là do số bị chia hay do số chia không phải số nguyên – một trong hai dòng nhập có thể gây ra lỗi đó. Nếu chúng ta muốn cung cấp cho người dùng phản hồi cụ thể hơn về đầu vào nào là sai, chúng ta sẽ phải bọc mỗi dòng đầu vào trong một try-except riêng biệt:

try:
    dividend = int(input("Please enter the dividend: "))
except ValueError:
    print("The dividend has to be a number!")

try:
    divisor = int(input("Please enter the divisor"))
except ValueError:
    print("The divisor has to be a number!")

try:
    print("%d / %d = %f" % (dividend, divisor, dividend/divisor))
except ZeroDivisionError:
    print("The dividend may not be zero!")

Nói chung, bạn nên sử dụng các lệnh try-except để bảo vệ các khối mã nhỏ chống lại các lỗi cụ thể hơn là bọc các khối mã lớn và viết mã khôi phục lỗi một cách chung chung, mơ hồ. Đôi khi có vẻ không hiệu quả và dài dòng khi viết nhiều câu lệnh try-except nhỏ thay vì một lệnh try-except duy nhất, nhưng chúng ta có thể giảm thiểu điều này ở mức độ nào đó bằng cách sử dụng hiệu quả các vòng lặp và hàm để giảm lượng trùng lặp mã.

Mệnh đề elsefinally

Có hai mệnh đề khác mà bạn có thể thêm vào khối try-except: elsefinally. else sẽ chỉ được thực thi nếu try không đưa ra ngoại lệ.

try:
    age = int(input("Please enter your age: "))
except ValueError:
    print("Hey, that wasn't a number!")
else:
    print("I see that you are %d years old." % age)

Mệnh đề finally luôn được chạy ở cuối try-except bất kể không có ngoại lệ hoặc có ngoại lệ, ngoại lệ được xử lý hay không được xử lý, khối được thoát ra bởi break, continue hoặc return. Chúng ta có thể sử dụng mệnh đề finally cho mã dọn dẹp mà chúng ta muốn luôn được thực thi.

try:
    age = int(input("Please enter your age: "))
except ValueError:
    print("Hey, that wasn't a numer!")
else:
    print("I see that you are %d years old." % age)
finally:
    print("It was really nice talking to you. Goodbye!")

Lệnh with

Sử dụng đối tượng ngoại lệ

Các đối tượng ngoại lệ của Python chứa nhiều thông tin hơn là chỉ loại lỗi. Chúng cũng đi kèm với một số loại thông báo – chúng ta đã thấy một số thông báo này được hiển thị khi chương trình của chúng ta gặp sự cố. Thường thì những thông báo này không thân thiện với người dùng lắm – nếu chúng ta muốn thông báo lỗi cho người dùng, chúng ta thường cần viết một thông báo mô tả hơn là giải thích lỗi liên quan đến những gì người dùng đã làm. Ví dụ: nếu lỗi do nhập sai, sẽ hữu ích nếu cho người dùng biết giá trị đầu vào nào không chính xác.

Đôi khi thông báo ngoại lệ chứa thông tin hữu ích mà chúng ta muốn hiển thị cho người dùng. Để truy cập thông báo, chúng ta cần truy cập được vào đối tượng ngoại lệ. Chúng ta có thể gán đối tượng cho một biến mà chúng ta có thể sử dụng trong khối except như sau:

try:
    age = int(input("Please enter your age: "))
except ValueError as err:
    print(err)

err không phải là chuỗi, nhưng Python biết phải chuyển đổi nó thành chuỗi như thế nào – chuỗi này biểu diễn thông báo của ngoại lệ, chính xác là cái chúng ta muốn. Chúng ta cũng có thể kết hợp thông báo của ngoại lệ với thông báo riêng:

try:
    age = int(input("Please enter your age: "))
except ValueError as err:
    print("You entered incorrect age input: %s" % err)

Tạo một ngoại lệ

Chúng ta có thể tự tạo một ngoại lệ bằng lệnh raise.

try:
    age = int(input("Please enter your age: "))
    if age < 0:
        raise ValueError("%d is not a valid age. Age must e positive or zero.")
except ValueError as err:
    print("You entered incorrect age input: %s" % err)
else:
    print("I see that you are %d years old." % age)

Đây là một số kiểu lỗi mà chúng ta có thể tạo ra trong mã:

  • TypeError: lỗi này cho biết rằng một biến có kiểu sai cho một phép toán. Chúng ta có thể tạo nó nếu một tham số không thuộc loại mà chúng ta biết cách xử lý.
  • ValueError: lỗi này được sử dụng để chỉ ra rằng một biến có kiểu đúng nhưng giá trị sai. Ví dụ, chúng ta đã sử dụng nó khi age là một số nguyên, nhưng không đúng là số dương.
  • NotImplementedError: chỉ ra rằng phương thức của một lớp phải được triển khai trong một lớp con.

Leave a Reply