Lỗi chuẩn

Python SELF VI
Mức độ , Bài học
Có sẵn

7.1 Sử dụng sai biểu thức như giá trị mặc định cho tham số hàm

Bạn đã học được nhiều thứ rồi, giờ hãy xem các lỗi cơ bản nhất của người mới bắt đầu – tốt hơn là học từ lỗi của người khác, đúng không?

Python cho phép bạn chỉ định rằng một hàm có thể có tham số không bắt buộc bằng cách gán cho chúng giá trị mặc định. Đây là một tính năng rất tiện lợi của ngôn ngữ, nhưng có thể dẫn đến những hệ quả không hay nếu kiểu giá trị đó thay đổi. Ví dụ, hãy xem định nghĩa hàm sau:


def foo(bar=[]):  # bar là tham số không bắt buộc
# và mặc định là danh sách rỗng.
    bar.append("baz")  # dòng này có thể gây rắc rối...
    return bar

Lỗi phổ biến ở đây là nghĩ rằng giá trị của tham số không bắt buộc sẽ được thiết lập thành giá trị mặc định mỗi lần hàm được gọi mà không có giá trị cho tham số này.

Trong mã trên, chẳng hạn, có thể bạn sẽ giả định rằng khi gọi lại hàm foo() (nghĩa là không chỉ định giá trị cho tham số bar), nó sẽ luôn trả về ["baz"], vì bạn nghĩ rằng mỗi lần foo() được gọi (không chỉ định tham số bar), bar sẽ được đặt thành [] (nghĩa là danh sách rỗng mới).

Nhưng hãy xem điều gì thực sự xảy ra:


result = foo()
print(result)  # ["baz"]
            
result = foo()
print(result)  # ["baz", "baz"]
            
result = foo()
print(result)  # ["baz", "baz", "baz"]

Tại sao hàm tiếp tục thêm giá trị baz vào danh sách đã tồn tại mỗi lần foo() được gọi, thay vì tạo danh sách mới mỗi lần?

Câu trả lời nằm ở việc hiểu sâu hơn về những gì đang diễn ra dưới "nắp ca-pô" của Python. Đặc biệt, giá trị mặc định cho hàm chỉ được khởi tạo một lần duy nhất trong quá trình định nghĩa hàm.

Vì vậy, tham số bar được khởi tạo mặc định (tức là danh sách rỗng) chỉ khi foo() được định nghĩa lần đầu, nhưng các lần gọi tiếp theo của foo() (không chỉ định tham số bar) sẽ tiếp tục sử dụng cùng danh sách đã được tạo ra cho tham số bar vào lúc đầu.

Để khắc phục, một "đường vòng" phổ biến cho lỗi này là định nghĩa như sau:


def foo(bar=None):             
    if bar is None:
        bar = []                                          
    bar.append("baz")   
    return bar
    
result = foo()
print(result)  # ["baz"]
    
result = foo()
print(result)  # ["baz"]
    
result = foo()
print(result)  # ["baz"]

7.2 Sử dụng sai biến lớp

Hãy xem ví dụ sau:


class A(object):
    x = 1
       
class B(A):
    pass
       
class C(A):
    pass
       
print(A.x, B.x, C.x) 
# 1 1 1

Có vẻ mọi thứ vẫn ổn.

Giờ hãy gán một giá trị cho trường x của lớp B:


B.x = 2
print(A.x, B.x, C.x)
# 1 2 1

Mọi thứ như dự kiến.

Bây giờ hãy thay đổi biến lớp A:


A.x = 3
print(A.x, B.x, C.x)
# 3 2 3

Thật là kỳ lạ, chỉ thay đổi A.x. Vậy tại sao C.x cũng thay đổi?

Trong Python, biến lớp được xử lý như từ điển và tuân theo cái thường được gọi là Thứ tự giải quyết phương thức (MRO). Như vậy, trong mã trên, vì thuộc tính x không được tìm thấy trong lớp C, nó sẽ được tìm thấy trong các lớp cơ bản của nó (chỉ A trong ví dụ trên, mặc dù Python hỗ trợ kế thừa nhiều).

Nói cách khác, C không có thuộc tính x riêng lẻ, độc lập với A. Vì vậy, các tham chiếu đến C.x thực sự là tham chiếu đến A.x. Điều này có thể gây ra vấn đề nếu không xem xét kỹ các trường hợp như vậy. Khi học Python, hãy chú ý đến các thuộc tính lớp và cách làm việc với chúng.

7.3 Chỉ định sai tham số cho khối ngoại lệ

Giả sử bạn có đoạn mã sau:


try:
    l = ["a", "b"]
    int(l[2])
except ValueError, IndexError:  # Để bắt cả hai ngoại lệ, phải không?
    pass
        
Traceback (most recent call last):
    File "<stdin>", line 3, in <module>
IndexError: list index out of range

Vấn đề ở đây là biểu thức except không chấp nhận danh sách các ngoại lệ được chỉ định theo cách này. Kết quả là, trong mã trên, ngoại lệ IndexError không bị chặn bởi biểu thức except; thay vào đó, ngoại lệ này kết thúc bằng cách liên kết với tham số tên là IndexError.

Quan trọng! Bạn có thể gặp phải mã như thế này trong các ví dụ trên mạng, vì nó được sử dụng trong Python 2.x.

Cách đúng để bắt nhiều ngoại lệ với biểu thức except là chỉ định tham số đầu tiên dưới dạng một bộ chứa tất cả các ngoại lệ cần bắt. Ngoài ra, để tăng tính dễ đọc, bạn có thể sử dụng từ khóa as, ví dụ như sau:


try:
    l = ["a", "b"]
    int(l[2])
except (ValueError, IndexError) as e:  
    pass
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION