Bài 8.4. Python - Truyền một List

Bạn sẽ thường thấy hữu ích khi truyền một danh sách cho một hàm, dù đó là danh sách tên, số, hoặc các đối tượng phức tạp hơn như từ điển. Khi bạn truyền một danh sách cho một hàm, hàm đó sẽ có quyền truy cập trực tiếp vào nội dung của danh sách. Hãy sử dụng các hàm để làm việc với danh sách hiệu quả hơn.

Giả sử chúng ta có một danh sách người dùng và muốn in ra lời chào cho từng người. Ví dụ sau đây gửi một danh sách tên cho một hàm gọi là greet_users(), hàm này sẽ chào từng người trong danh sách một cách riêng biệt:

def greet_users(names):
    """In ra lời chào đơn giản cho từng người dùng trong danh sách."""
    for name in names:
        msg = f"Hello, {name.title()}!"
        print(msg)

usernames = ['hannah', 'ty', 'margot']
greet_users(usernames)

Chúng ta định nghĩa greet_users() để nó mong đợi một danh sách tên, danh sách này được gán cho tham số names. Hàm lặp qua danh sách mà nó nhận được và in ra lời chào cho từng người dùng. Bên ngoài hàm, chúng ta định nghĩa một danh sách người dùng và sau đó truyền danh sách usernames cho greet_users() trong lệnh gọi hàm:

Hello, Hannah!
Hello, Ty!
Hello, Margot!

Đây là đầu ra mà chúng ta mong muốn. Mỗi người dùng thấy một lời chào cá nhân hóa, và bạn có thể gọi hàm bất cứ khi nào bạn muốn chào một tập hợp người dùng cụ thể.

Sửa đổi một danh sách trong hàm

Khi bạn truyền một danh sách cho một hàm, hàm có thể sửa đổi danh sách đó. Bất kỳ thay đổi nào được thực hiện đối với danh sách bên trong thân hàm đều là vĩnh viễn, cho phép bạn làm việc hiệu quả ngay cả khi bạn đang xử lý một lượng lớn dữ liệu.

Hãy xem xét một công ty tạo ra các mô hình in 3D từ các thiết kế mà người dùng gửi. Các thiết kế cần được in được lưu trữ trong một danh sách, và sau khi được in, chúng được chuyển sang một danh sách riêng. Mã sau đây thực hiện điều này mà không sử dụng hàm:

# Bắt đầu với một số thiết kế cần được in.
unprinted_designs = ['phone case', 'robot pendant', 'dodecahedron']
completed_models = []

# Mô phỏng việc in từng thiết kế, cho đến khi không còn thiết kế nào.
# Chuyển từng thiết kế sang completed_models sau khi in.
while unprinted_designs:
    current_design = unprinted_designs.pop()
    print(f"Printing model: {current_design}")
    completed_models.append(current_design)

# Hiển thị tất cả các mô hình đã được in.
print("\nThe following models have been printed:")
for completed_model in completed_models:
    print(completed_model)

Chương trình này bắt đầu với một danh sách các thiết kế cần được in và một danh sách trống gọi là completed_models mà mỗi thiết kế sẽ được chuyển sang sau khi nó đã được in. Miễn là còn thiết kế trong unprinted_designs, vòng lặp while mô phỏng việc in từng thiết kế bằng cách loại bỏ một thiết kế từ cuối danh sách, lưu trữ nó trong current_design, và hiển thị một thông báo rằng thiết kế hiện tại đang được in. Sau đó, nó thêm thiết kế vào danh sách các mô hình đã hoàn thành. Khi vòng lặp kết thúc, một danh sách các thiết kế đã được in được hiển thị:

Printing model: dodecahedron
Printing model: robot pendant
Printing model: phone case
The following models have been printed:
dodecahedron
robot pendant
phone case

Chúng ta có thể tổ chức lại mã này bằng cách viết hai hàm, mỗi hàm thực hiện một công việc cụ thể. Hầu hết mã sẽ không thay đổi; chúng ta chỉ cần cấu trúc nó cẩn thận hơn. Hàm đầu tiên sẽ xử lý việc in các thiết kế, và hàm thứ hai sẽ tóm tắt các bản in đã được thực hiện:

def print_models(unprinted_designs, completed_models):
    """
    Mô phỏng việc in từng thiết kế, cho đến khi không còn thiết kế nào.
    Chuyển từng thiết kế sang completed_models sau khi in.
    """
    while unprinted_designs:
        current_design = unprinted_designs.pop()
        print(f"Printing model: {current_design}")
        completed_models.append(current_design)

def show_completed_models(completed_models):
    """Hiển thị tất cả các mô hình đã được in."""
    print("\nThe following models have been printed:")
    for completed_model in completed_models:
        print(completed_model)

unprinted_designs = ['phone case', 'robot pendant', 'dodecahedron']
completed_models = []
print_models(unprinted_designs, completed_models)
show_completed_models(completed_models)

Chúng ta định nghĩa hàm print_models() với hai tham số: một danh sách các thiết kế cần được in và một danh sách các mô hình đã hoàn thành. Với hai danh sách này, hàm mô phỏng việc in từng thiết kế bằng cách làm trống danh sách các thiết kế chưa in và làm đầy danh sách các mô hình đã hoàn thành. Chúng ta sau đó định nghĩa hàm show_completed_models() với một tham số: danh sách các mô hình đã hoàn thành. Với danh sách này, show_completed_models() hiển thị tên của từng mô hình đã được in.

Chương trình này có cùng đầu ra như phiên bản không sử dụng hàm, nhưng mã được tổ chức tốt hơn nhiều. Mã thực hiện hầu hết công việc đã được chuyển vào hai hàm riêng biệt, điều này làm cho phần chính của chương trình dễ hiểu hơn. Hãy nhìn vào thân chương trình và nhận thấy bạn có thể dễ dàng theo dõi những gì đang xảy ra như thế nào:

unprinted_designs = ['phone case', 'robot pendant', 'dodecahedron']
completed_models = []
print_models(unprinted_designs, completed_models)
show_completed_models(completed_models)

Chúng ta thiết lập một danh sách các thiết kế chưa in và một danh sách trống sẽ chứa các mô hình đã hoàn thành. Sau đó, vì chúng ta đã định nghĩa hai hàm của mình, tất cả những gì chúng ta cần làm là gọi chúng và truyền cho chúng các đối số đúng. Chúng ta gọi print_models() và truyền cho nó hai danh sách mà nó cần; như mong đợi, print_models() mô phỏng việc in các thiết kế. Sau đó, chúng ta gọi show_completed_models() và truyền cho nó danh sách các mô hình đã hoàn thành để nó có thể báo cáo các mô hình đã được in. Các tên hàm mô tả cho phép người khác đọc mã này và hiểu nó, ngay cả khi không có chú thích.

Chương trình này dễ mở rộng và bảo trì hơn so với phiên bản không sử dụng hàm. Nếu chúng ta cần in thêm các thiết kế sau này, chúng ta chỉ cần gọi lại print_models(). Nếu chúng ta nhận ra mã in cần được sửa đổi, chúng ta có thể thay đổi mã một lần, và các thay đổi của chúng ta sẽ có hiệu lực ở mọi nơi mà hàm được gọi. Kỹ thuật này hiệu quả hơn so với việc phải cập nhật mã riêng biệt ở nhiều nơi trong chương trình.

Ví dụ này cũng minh họa ý tưởng rằng mỗi hàm nên có một công việc cụ thể. Hàm đầu tiên in từng thiết kế, và hàm thứ hai hiển thị các mô hình đã hoàn thành. Điều này có lợi hơn so với việc sử dụng một hàm để thực hiện cả hai công việc. Nếu bạn đang viết một hàm và nhận thấy hàm đang thực hiện quá nhiều tác vụ khác nhau, hãy thử chia mã thành hai hàm. Hãy nhớ rằng bạn luôn có thể gọi một hàm từ một hàm khác, điều này có thể hữu ích khi chia một tác vụ phức tạp thành một loạt các bước.

Ngăn chặn một hàm sửa đổi danh sách

Đôi khi bạn sẽ muốn ngăn chặn một hàm sửa đổi danh sách. Ví dụ, giả sử bạn bắt đầu với một danh sách các thiết kế chưa in và viết một hàm để chuyển chúng sang một danh sách các mô hình đã hoàn thành, như trong ví dụ trước. Bạn có thể quyết định rằng mặc dù bạn đã in tất cả các thiết kế, bạn muốn giữ lại danh sách ban đầu của các thiết kế chưa in cho hồ sơ của mình. Nhưng vì bạn đã chuyển tất cả các tên thiết kế ra khỏi unprinted_designs, danh sách bây giờ trống, và danh sách trống là phiên bản duy nhất bạn có; bản gốc đã biến mất. Trong trường hợp này, bạn có thể giải quyết vấn đề này bằng cách truyền cho hàm một bản sao của danh sách, không phải bản gốc. Bất kỳ thay đổi nào mà hàm thực hiện đối với danh sách sẽ chỉ ảnh hưởng đến bản sao, để lại danh sách gốc nguyên vẹn.

Bạn có thể gửi một bản sao của danh sách cho một hàm như sau:

function_name(list_name[:])

Ký hiệu lát cắt [:] tạo một bản sao của danh sách để gửi cho hàm. Nếu chúng ta không muốn làm trống danh sách các thiết kế chưa in trong printing_models.py, chúng ta có thể gọi print_models() như sau:

print_models(unprinted_designs[:], completed_models)

Hàm print_models() có thể thực hiện công việc của nó vì nó vẫn nhận được tên của tất cả các thiết kế chưa in. Nhưng lần này nó sử dụng một bản sao của danh sách các thiết kế chưa in ban đầu, không phải danh sách unprinted_designs thực tế. Danh sách completed_models sẽ được làm đầy với tên của các mô hình đã in như trước, nhưng danh sách ban đầu của các thiết kế chưa in sẽ không bị ảnh hưởng bởi hàm.

Mặc dù bạn có thể bảo toàn nội dung của một danh sách bằng cách truyền một bản sao của nó cho các hàm của bạn, bạn nên truyền danh sách gốc cho các hàm trừ khi bạn có lý do cụ thể để truyền một bản sao. Sẽ hiệu quả hơn khi một hàm làm việc với một danh sách hiện có, vì điều này tránh sử dụng thời gian và bộ nhớ cần thiết để tạo một bản sao riêng biệt. Điều này đặc biệt đúng khi làm việc với các danh sách lớn.

Comments

There are no comments at the moment.