0
0
Lập trình
Flame Kris
Flame Krisbacodekiller

Thảm Họa Ngày Di Chuyển: Hiểu Về Biến Đổi Danh Sách Trong Python

Đăng vào 1 tháng trước

• 6 phút đọc

Thảm Họa Ngày Di Chuyển: Hiểu Về Biến Đổi Danh Sách Trong Python

Timothy, người quản lý thư viện, nghĩ rằng mình đã hiểu rõ hệ thống kệ sách của mình. Nhưng rồi ngày hôm đó đã thay đổi mọi thứ—ngày anh phát hiện ra rằng một số kệ sách không như chúng ta nghĩ.


Kệ Sách Chia Sẻ Bí Ẩn

Hãy tưởng tượng Timothy đang quản lý các bộ sưu tập đặc biệt của thư viện. Anh tạo ra hai kệ sách mà anh nghĩ là riêng biệt cho các nhóm đọc khác nhau:

python Copy
# Timothy thiết lập tài liệu đọc
classics = ["Kiêu Hãnh và Định Kiến", "Jane Eyre"]
book_club_a = classics
book_club_b = classics

"Tuyệt vời!" Timothy nghĩ. "Hai nhóm đọc, mỗi nhóm có kệ sách riêng." Nhưng khi Book Club A quyết định họ muốn thêm "Đồi Gió Hú" vào bộ sưu tập của mình, điều gì đó kỳ lạ đã xảy ra:

python Copy
book_club_a.append("Đồi Gió Hú")
print(book_club_b)  # ["Kiêu Hãnh và Định Kiến", "Jane Eyre", "Đồi Gió Hú"]

Bất ngờ, Book Club B cũng có cuốn sách mới! Timothy nhìn chằm chằm trong sự bối rối. Làm thế nào sự bổ sung của một nhóm lại xuất hiện trong bộ sưu tập của nhóm khác?

Sự Thật Về Nhãn Kệ Sách

Đây là sự khám phá của Timothy: anh không tạo ra những kệ sách riêng biệt chút nào. Anh đã tạo ra nhiều nhãn gắn liền với cùng một kệ sách vật lý. Trong thế giới Python, khi bạn viết book_club_a = classics, bạn không đang sao chép kệ sách—bạn chỉ đang tạo một nhãn tên mới chỉ về kệ sách hiện có.

Hãy tưởng tượng như thế này: Timothy có một kệ sách ở góc phòng, nhưng anh đã treo ba biển hiệu khác nhau lên nó: "classics", "book_club_a", và "book_club_b." Khi bất cứ ai thêm hoặc gỡ bỏ sách bằng bất kỳ biển hiệu nào, họ đều đang làm việc với cùng một kệ sách vật lý.

Khủng Hoảng Danh Tính

Timothy phát triển một bài kiểm tra đơn giản để kiểm tra xem hai nhãn có chỉ về cùng một kệ sách hay không. Anh gọi đó là "kiểm tra danh tính":

python Copy
# Kiểm tra danh tính của Timothy
print(book_club_a is classics)  # True - cùng một kệ sách!
print(id(book_club_a))         # Hiện số duy nhất của kệ sách
print(id(classics))            # Cùng số - đã xác nhận!

Toán tử is cho Timothy biết liệu hai cái tên có tham chiếu đến cùng một kệ sách hay không, trong khi id() hiển thị mỗi kệ sách một số nhận dạng duy nhất—giống như số sê-ri được đóng dấu trên khung.

Kỹ Thuật Nhân Bản Đúng Cách

Khi Timothy thực sự cần kệ sách riêng biệt, anh học cách tạo ra các bản sao thực sự:

python Copy
# Phương pháp 1: Sao chép bằng cắt
book_club_c = classics[:]      # Tạo ra một kệ sách mới hoàn toàn

# Phương pháp 2: Phương pháp copy  
book_club_d = classics.copy()  # Cũng tạo ra một kệ sách mới

# Phương pháp 3: Constructor danh sách
book_club_e = list(classics)   # Một cách khác để tạo kệ sách mới

Giờ đây, khi Book Club C thêm "Emma", các bộ sưu tập khác vẫn không bị ảnh hưởng. Timothy đã học được cách nhân bản kệ sách thực sự, không chỉ thêm nhãn mới.

Bẫy Tham Số Hàm

Sự ngạc nhiên lớn nhất của Timothy đến khi anh tạo ra một hàm để tổ chức sách:

python Copy
def add_bestseller(bookcase):
    bookcase.append("Cuốn Sách Vĩ Đại")
    return bookcase

my_books = ["1984", "Thế Giới Mới Tuyệt Vời"]
updated_books = add_bestseller(my_books)

print(my_books)        # ["1984", "Thế Giới Mới Tuyệt Vời", "Cuốn Sách Vĩ Đại"]
print(updated_books)   # ["1984", "Thế Giới Mới Tuyệt Vời", "Cuốn Sách Vĩ Đại"]

Timothy mong đợi my_books sẽ không thay đổi, nhưng hàm đã sửa đổi kệ sách gốc của anh! Điều này xảy ra vì Python truyền kệ sách chính nó (không phải bản sao) đến hàm. Khi hàm thêm một cuốn sách, nó đang thêm vào kệ sách gốc.

Mô Hình Hàm An Toàn

Timothy phát triển một cách tiếp cận an toàn hơn cho các hàm cần sửa đổi kệ sách:

python Copy
def add_bestseller_safely(bookcase):
    new_bookcase = bookcase.copy()  # Tạo bản sao trước tiên
    new_bookcase.append("Cuốn Sách Vĩ Đại")
    return new_bookcase

my_books = ["1984", "Thế Giới Mới Tuyệt Vời"]
updated_books = add_bestseller_safely(my_books)

print(my_books)        # ["1984", "Thế Giới Mới Tuyệt Vời"] - không thay đổi!
print(updated_books)   # ["1984", "Thế Giới Mới Tuyệt Vời", "Cuốn Sách Vĩ Đại"]

Giờ đây, bộ sưu tập gốc của Timothy vẫn an toàn trong khi anh trả về một bản sao đã sửa đổi.

Sự Phức Tạp Của Kệ Sách Lồng Nhau

Ngay khi Timothy nghĩ rằng mình đã hiểu mọi thứ, anh gặp phải những kệ sách lồng nhau—các kệ chứa các kệ khác:

python Copy
# Một kệ sách chứa các kệ sách nhỏ hơn
departments = [
    ["Tiểu Thuyết", "Phi Tiểu Thuyết"],      # Bộ phận văn học
    ["Lịch Sử", "Tiểu Sử"]         # Bộ phận tham khảo  
]

backup = departments.copy()
departments[0].append("Thơ")

print(backup)  # [["Tiểu Thuyết", "Phi Tiểu Thuyết", "Thơ"], ["Lịch Sử", "Tiểu Sử"]]

Phương pháp copy() chỉ sao chép kệ sách chính, không sao chép các kệ nhỏ bên trong! Timothy phát hiện ra rằng điều này được gọi là "sao chép nông"—nó sao chép chứa mà không sao chép nội dung bên trong.

Để có những kệ sách lồng nhau độc lập thực sự, Timothy học cần đến Bộ Phận Sao Chép Sâu:

python Copy
import copy

# Timothy sử dụng thiết bị sao chép sâu đặc biệt
departments = [
    ["Tiểu Thuyết", "Phi Tiểu Thuyết"],
    ["Lịch Sử", "Tiểu Sử"]
]

true_backup = copy.deepcopy(departments)
departments[0].append("Thơ")

print(true_backup)  # [["Tiểu Thuyết", "Phi Tiểu Thuyết"], ["Lịch Sử", "Tiểu Sử"]]

Giờ đây, bản sao vẫn hoàn toàn không thay đổi! Hàm deepcopy() tạo ra các kệ sách mới từ đầu đến cuối, ngay cả khi sao chép các kệ nhỏ bên trong kệ chính.

Hướng Dẫn Biến Đổi Của Timothy

Sau thảm họa ngày di chuyển, Timothy thiết lập rõ ràng các quy tắc cho thư viện:

Khi bạn muốn kệ sách riêng biệt:

  • Sử dụng new_list = old_list.copy() cho các bộ sưu tập đơn giản
  • Sử dụng new_list = old_list[:] cho kết quả tương tự
  • Lưu ý rằng các bộ sưu tập lồng nhau cần xử lý đặc biệt

Khi kiểm tra xem các kệ sách có giống nhau không:

  • Sử dụng list1 is list2 để kiểm tra danh tính (cùng một kệ sách)
  • Sử dụng list1 == list2 để kiểm tra nội dung (cùng sách)

Khi viết các hàm:

  • Quyết định xem có sửa đổi kệ sách gốc hay không hoặc trả về một cái mới
  • Làm cho lựa chọn của bạn rõ ràng trong tên hàm và tài liệu
  • Khi không chắc chắn, hãy sao chép trước để tránh bất ngờ

Cái nhìn sâu sắc chính mà Timothy đã học được: trong Python, việc gán tạo ra nhãn mới cho các kệ sách hiện có, không phải là các kệ sách mới. Hiểu sự khác biệt này giúp ngăn chặn vô số cơn đau đầu và lỗi bí ẩn.

Hãy nhớ: mỗi khi bạn thấy hành vi kỳ lạ mà việc thay đổi một biến lại ảnh hưởng đến biến khác, hãy tự hỏi—liệu đây có thực sự là những kệ sách riêng biệt, hay chỉ là những nhãn khác nhau trên cùng một cái?


Aaron Rose là một kỹ sư phần mềm và tác giả công nghệ tại tech-reader.blog và là tác giả của Think Like a Genius.

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào