Hôm nay, tôi muốn chia sẻ một lời khuyên cứu mạng nhanh chóng về một lỗi phổ biến mà các nhà phát triển Python vẫn có xu hướng mắc phải
TL;DR
Không sử dụng các đối số mặc định có thể thay đổi trong Python, trừ khi bạn có lý do THỰC SỰ chính đáng để làm như vậy
Tại sao?
Thay vào đó, hãy đặt mặc định là None
và gán giá trị có thể thay đổi bên trong hàm
Giờ kể chuyện
Tôi đã gặp lỗi này một vài lần trong hành trình phát triển ban đầu của mình
- Khi tôi mới bắt đầu thực hiện các dự án thực sự với Python [chắc 5-6 năm trước], tôi nhớ mình đã vấp phải một lỗi rất lạ. Một danh sách sẽ lớn hơn dự kiến khi một chức năng được gọi nhiều lần, điều này sẽ gây ra các lỗi lạ
- Khi còn học đại học, chúng tôi được giao một dự án phát triển thuật toán và người hướng dẫn đã gửi cho chúng tôi một chương trình để kiểm tra mã của chúng tôi. Tại một thời điểm, tôi hoàn toàn tin rằng mã của mình là chính xác, nhưng các bài kiểm tra vẫn thất bại
Trong trường hợp đầu tiên, tôi, một nhà phát triển trẻ đang học cách gỡ lỗi, đã rất tức giận vì anh ấy không biết tại sao danh sách đó lại ngày càng nhiều và anh ấy đã dành hàng giờ để cố gắng khắc phục sự cố này.
Trong trường hợp thứ hai, các sinh viên khác cũng gặp phải sự cố và người hướng dẫn cũng không biết gì, vì vậy thời gian của mọi người đã bị lãng phí
Trong cả hai trường hợp, thời gian và công sức có thể đã được tiết kiệm nếu chúng ta biết về sai lầm phổ biến này
Chúng ta đã làm gì sai?
Hóa ra là trong cả hai trường hợp, một danh sách trống đã được sử dụng làm đối số mặc định cho một hàm. Ừ, chỉ vậy thôi. Cái gì đó như
def compute_patterns[inputs=[]]:
inputs.append["some stuff"]
patterns = ["a list based on"] + inputs
return patterns
Hãy tự mình thử. nếu bạn chạy chức năng này nhiều lần, bạn sẽ nhận được các kết quả khác nhau
>>> compute_patterns[]
['a list based on', 'some stuff'] # Expected
>>> compute_patterns[]
['a list based on', 'some stuff', 'some stuff'] # Woops!
Mặc dù trông không có vẻ gì nhiều, nhưng inputs=[]
chính là cậu bé nghịch ngợm đã gây ra mớ hỗn độn này
Vấn đề
Trong Python, khi chuyển một giá trị có thể thay đổi làm đối số mặc định trong hàm, đối số mặc định sẽ bị thay đổi bất cứ khi nào giá trị đó bị thay đổi
Ở đây, "giá trị có thể thay đổi" đề cập đến bất kỳ thứ gì như danh sách, từ điển hoặc thậm chí là thể hiện của lớp
Điều gì đã xảy ra, chính xác?
Không dễ hiểu tại sao thực tế trên có thể là một vấn đề
Đây là những gì xảy ra chi tiết bằng cách sử dụng một ví dụ khác
def append[element, seq=[]]:
seq.append[element]
return seq
>>> append[1] # seq is assigned to []
[1] # This returns a reference the *same* list as the default for `seq`
>>> append[2] # => `seq` is now given [1] as a default!
[1, 2] # WTFs and headaches start here…
Như bạn có thể thấy, Python "giữ lại" giá trị mặc định và liên kết nó với hàm theo một cách nào đó. Vì nó bị biến đổi bên trong hàm, nên nó cũng được biến đổi như một mặc định. Cuối cùng, chúng tôi sử dụng một giá trị mặc định khác nhau mỗi khi hàm được gọi — duh
Giải pháp
Câu chuyện dài, giải pháp rất đơn giản
Sử dụng None
làm giá trị mặc định và gán giá trị có thể thay đổi bên trong hàm
Vì vậy, thay vào đó, hãy làm điều này
# 👇
def append[element, seq=None]:
if seq is None: # 👍
seq = []
seq.append[element]
return seq
>>> append[1] # `seq` is assigned to []
[1]
>>> append[2] # `seq` is assigned to [] again!
[2] # Yay!
Đây thực sự là một mẫu rất phổ biến trong Python;
Nó phổ biến đến mức có một Đề xuất cải tiến Python [PEP] hiện đang được thực hiện [PEP 505 - Toán tử không nhận biết], trong số những thứ khác, cho phép đơn giản hóa mã ở trên và chỉ cần viết
def append[element, seq=None]:
seq ??= [] # ✨
seq.append[element]
return seq
PEP vẫn là một bản nháp, nhưng tôi chỉ muốn đề cập đến nó vì tôi thực sự hy vọng các toán tử không nhận biết sẽ sớm trở thành một thứ trong Python. 🔥
bài học kinh nghiệm
của bạn đi. Hy vọng rằng bạn sẽ không bao giờ thấy mình sử dụng các giá trị mặc định có thể thay đổi trong mã Python nữa [trừ khi bạn muốn làm phiền mọi người]