Bất cứ ai cũng mày mò với Python đủ lâu đã bị cắn (hoặc bị xé thành từng mảnh) bởi vấn đề sau:
def foo(a=[]):
a.append(5)
return a
Người mới dùng Python sẽ mong đợi hàm này luôn trả về một danh sách chỉ có một phần tử : [5]
. Thay vào đó, kết quả rất khác biệt và rất đáng kinh ngạc (đối với người mới):
>>> foo()
[5]
>>> foo()
[5, 5]
>>> foo()
[5, 5, 5]
>>> foo()
[5, 5, 5, 5]
>>> foo()
Một người quản lý của tôi đã có lần gặp gỡ đầu tiên với tính năng này và gọi nó là "lỗ hổng thiết kế ấn tượng" của ngôn ngữ. Tôi đã trả lời rằng hành vi đó có một lời giải thích cơ bản, và nó thực sự rất khó hiểu và bất ngờ nếu bạn không hiểu nội bộ. Tuy nhiên, tôi không thể trả lời (cho chính mình) câu hỏi sau: lý do ràng buộc đối số mặc định ở định nghĩa hàm và không phải khi thực hiện chức năng là gì? Tôi nghi ngờ hành vi có kinh nghiệm có một cách sử dụng thực tế (ai thực sự sử dụng biến tĩnh trong C, không có lỗi sinh sản?)
Chỉnh sửa :
Baczek đã làm một ví dụ thú vị. Cùng với hầu hết các bình luận của bạn và của Utaal nói riêng, tôi đã nói rõ hơn:
>>> def a():
... print("a executed")
... return []
...
>>>
>>> def b(x=a()):
... x.append(5)
... print(x)
...
a executed
>>> b()
[5]
>>> b()
[5, 5]
Đối với tôi, dường như quyết định thiết kế có liên quan đến nơi đặt phạm vi của các tham số: bên trong hàm hoặc "cùng" với nó?
Thực hiện liên kết bên trong hàm có nghĩa x
là được ràng buộc một cách hiệu quả với mặc định đã chỉ định khi hàm được gọi, không được xác định, một cái gì đó sẽ có một lỗ hổng sâu: def
dòng sẽ là "lai" theo nghĩa là một phần của liên kết (của đối tượng hàm) sẽ xảy ra ở định nghĩa và một phần (gán tham số mặc định) tại thời điểm gọi hàm.
Hành vi thực tế phù hợp hơn: mọi thứ của dòng đó được đánh giá khi dòng đó được thực thi, nghĩa là tại định nghĩa hàm.
[5]
." Tôi là người mới làm quen với Python và tôi sẽ không mong đợi điều này, vì rõ ràng foo([1])
sẽ quay trở lại [1, 5]
chứ không phải [5]
. Điều bạn muốn nói là một người mới sẽ mong đợi hàm được gọi không có tham số sẽ luôn trả về [5]
.