Cho dù bạn là một tân binh hay một lập trình viên Python có kinh nghiệm, rất có thể bạn đã từng sử dụng phép nhân danh sách và/hoặc đọc về nó trong các bài viết kiểu “các tính năng thú vị của Python” đó
Điều này là do nó chắc chắn là một trong những tính năng thú vị được thiết kế để giúp cuộc sống Python của bạn dễ dàng hơn
Điểm hay của phép nhân danh sách là nó trừu tượng hóa quá trình khởi tạo danh sách
Thay vì sử dụng cách tiếp cận lặp lại hoặc hiểu danh sách
# Iterative approach
my_list = []
for _ in range[N]:
my_list.append[n]# List comprehension
my_list = [n for _in range[N]]
bạn có thể nhận được kết quả tương tự với
my_list = [n] * N
Bạn thấy đấy, tôi đã học các ngôn ngữ lập trình theo thứ tự sau
C -> C++ -> MATLAB -> R -> Python.
Không có ngôn ngữ lập trình nào khác mà tôi đã làm việc trước Python thậm chí có thể cung cấp từ xa sự ngắn gọn và trực quan như vậy
Tuy nhiên, khi tôi bắt đầu viết mã ngày càng phức tạp hơn, phép nhân danh sách bắt đầu khiến tôi lo lắng.
Tôi nhớ rằng có lần tôi đã dành cả buổi chiều để gỡ lỗi mã chỉ để phát hiện ra rằng vấn đề bắt nguồn từ việc tạo danh sách không đúng cách bằng cách sử dụng toán tử
my_list = [n] * N
3Do đó, tôi cảm thấy cần phải thảo luận về vấn đề này vì tôi biết thực tế là một số nhà phát triển vẫn chưa biết về sự đánh đổi đi kèm với toán tử ngôi sao khi tạo danh sách
Có gì sai với phép nhân danh sách
Hãy xem xét đoạn mã sau
>>> my_list = [0] * 3
>>> my_list[0] = 1
>>> my_list
[1, 0, 0]
Đây là những gì bạn mong đợi. Càng xa càng tốt
Bây giờ, hãy thử tạo một mảng 2D bằng cách sử dụng phương pháp tương tự
>>> my_list = [[0] * 3] * 5
>>> my_list[0][0] = 1
>>> my_list
[[1, 0, 0], [1, 0, 0], [1, 0, 0], [1, 0, 0], [1, 0, 0]]
Hừm. Đây có lẽ không phải là điều bạn muốn
Làm thế nào về việc đẩy nó hơn nữa bằng cách khởi tạo một mảng 3D
>>> my_list = [[[0] * 3] * 5] * 2
>>> my_list[0][0][0] = 1
>>> my_list
[[[1, 0, 0], [1, 0, 0], [1, 0, 0], [1, 0, 0], [1, 0, 0]], [[1, 0, 0], [1, 0, 0], [1, 0, 0], [1, 0, 0], [1, 0, 0]]]
Đầu ra dự kiến sẽ là cập nhật giá trị đầu tiên của danh sách con
my_list = [n] * N
4. Tuy nhiên, có vẻ như bản cập nhật đã được sao chép trên tất cả các danh sách phụVì vậy, tại sao điều này xảy ra?
Phép nhân danh sách hoạt động như thế nào?
Để hiểu hành vi trước đó, bạn nên xem lại Câu hỏi thường gặp của Python, trong đó nói
“Lý do là việc sao chép danh sách với
my_list = [n] * N
3 không tạo bản sao, nó chỉ tạo tham chiếu đến các đối tượng hiện có. ”
Hãy dịch mã này thành mã để hiểu rõ hơn về cách Python hoạt động bên dưới mui xe
- Liệt kê phép nhân
my_list = [[0] * 5] * 5
for i in range[5]:
print[id[my_list[i]]]
đầu ra
my_list = [n] * N
0- Sử dụng vòng lặp for
my_list = [n] * N
1đầu ra
my_list = [n] * N
2- Diễn dịch
Không giống như vòng lặp
my_list = [n] * N
6, tất cả các danh sách được sao chép thông qua toán tử my_list = [n] * N
3 trỏ đến cùng một địa chỉ bộ nhớ. Điều này có nghĩa là bất kỳ thay đổi nào ảnh hưởng đến một danh sách lồng nhau sẽ ảnh hưởng đến tất cả các danh sách khác, điều này rõ ràng là đi ngược lại mục đích ban đầu của chúng taCâu hỏi bây giờ trở thành
- Tại sao ví dụ đầu tiên
my_list = [n] * N
8 hoạt động tốt, mặc dù tất cả các thành phần của danh sách đều tham chiếu đến cùng một đối tượng?
Hóa ra lý do đằng sau hành vi này [như đã nêu trong Python wikibook] là do danh sách là các mục có thể thay đổi trong khi ________ 19 và những thứ tương tự là bất biến. kiểm tra cái này
Nguồn
Và vì các đối tượng bất biến không thể thay đổi, Python tạo một tham chiếu mới [khác] cho đối tượng khi bạn cập nhật một mục trong danh sách
my_list = [n] * N
0cách giải quyết
Một cách khắc phục nhanh chóng và dễ dàng cho vấn đề này là sử dụng tính năng hiểu danh sách. Tất nhiên, đây là phần bổ sung cho vòng lặp
my_list = [n] * N
6 tiêu chuẩnmy_list = [n] * N
1Ngoài ra, chúng ta có thể thấy rằng một địa chỉ bộ nhớ khác đã được phân bổ cho mỗi danh sách
my_list = [n] * N
2Quan trọng hơn, phương pháp này hoạt động tốt trong mọi tình huống
Vì vậy, tại sao bạn không kiên trì và chơi an toàn hơn là phải suy nghĩ kỹ trước khi sử dụng phép nhân danh sách?
Sự kết luận
Tôi không phải là một fan hâm mộ lớn của đường cú pháp Pythonic
Vâng, tôi đồng ý rằng nó tạo ra một mã ngắn gọn và súc tích
Tuy nhiên, khi nào có
C -> C++ -> MATLAB -> R -> Python.
1 trong ngành công nghiệp phần mềm?Trên thực tế, càng viết mã bằng Python, tôi càng thấy mình nghiêng về việc sử dụng cú pháp tiêu chuẩn của Python và bỏ qua các phím tắt
Vào cuối ngày, điều quan trọng là hiệu suất, khả năng bảo trì và khả năng đọc. Không phải bạn có bao nhiêu dòng mã
Và nếu bạn không thể làm gì nếu không có phím tắt, thì ít nhất hãy đọc cái hay, cái dở và cái xấu của cú pháp bạn đang sử dụng. Trong trường hợp này, một sự hiểu biết vừa phải về các khái niệm công nghệ phần mềm [i. e. , cấu trúc dữ liệu, cấp phát bộ nhớ…] có thể được yêu cầu
Mã hóa vui vẻ
Nếu bạn thấy điều này hữu ích, hãy cân nhắc trở thành thành viên cao cấp. Nếu bạn sử dụng liên kết này, tôi sẽ nhận được một vết cắt nhỏ