Truyền Tham Chiếu trong Python: Trường Hợp Sử Dụng Mutable và Immutable

1. Sự khác biệt giữa Truyền theo Giá trị và Truyền theo Tham chiếu

Trong Python, có hai cách truyền đối số cho hàm: truyền theo giá trị và truyền theo tham chiếu.

  • Pass-by-value : Phương pháp truyền một bản sao của giá trị cho hàm dưới dạng đối số; việc sửa đổi tham số bên trong hàm không ảnh hưởng đến biến gốc.
  • Pass-by-reference : Phương pháp truyền một tham chiếu (địa chỉ) tới biến cho hàm, vì vậy các thay đổi bên trong hàm được phản ánh trong biến gốc.

Trong Python, hành vi này thay đổi tùy thuộc vào tính chất của đối tượng. Hành vi “truyền theo tham chiếu” của Python đặc biệt áp dụng cho các kiểu dữ liệu có thể thay đổi và có thể ảnh hưởng đáng kể đến cách mã hoạt động, vì vậy việc hiểu đúng là rất quan trọng.

Ad

2. Đặc điểm của Đối tượng trong Python

Trong Python, mọi dữ liệu đều được coi là đối tượng, và dựa trên đặc điểm của chúng chúng được phân loại thành bất biến (không thể thay đổi) hoặc có thể thay đổi (có thể thay đổi). Sự phân biệt này ảnh hưởng đến cách chúng hoạt động khi được truyền làm đối số cho hàm.

  • Immutable objects : các đối tượng không thể sửa đổi sau khi tạo. Ví dụ bao gồm số nguyên (int), số thực (float), chuỗi (str), tuple (tuple). Vì các kiểu dữ liệu này không thể thay đổi một khi đã tạo, các thao tác trên chúng trong hàm không ảnh hưởng tới người gọi ngay cả khi được truyền theo tham chiếu.
  • Mutable objects : các đối tượng có thể được sửa đổi sau khi tạo. Ví dụ bao gồm danh sách (list), từ điển (dict), tập hợp (set). Vì các thay đổi đối với các kiểu dữ liệu này trong hàm được phản ánh trong người gọi, chúng đặc biệt chịu ảnh hưởng của hành vi truyền tham chiếu của Python.

Ad
RUNTEQ(ランテック)|超実戦型エンジニア育成スクール

3. Cách Python Truyền Đối số

Trong Python, khi bạn truyền đối số cho một hàm, các tham chiếu tới các đối tượng được truyền. Hành vi này đôi khi được gọi là “truyền theo tham chiếu đối tượng”. Từ đây, chúng ta sẽ đi qua các ví dụ cụ thể minh họa hành vi của Python tùy thuộc vào việc đối tượng là bất biến hay có thể thay đổi.

3.1 Đối tượng Bất biến

Khi bạn truyền một đối tượng bất biến cho hàm, việc gán một giá trị mới bên trong hàm sẽ tạo ra một đối tượng mới và không ảnh hưởng tới đối tượng gốc.

Ví dụ: Truyền một Số Nguyên Bất biến

def modify_number(num):
    num = num * 2
    print("Number inside the function:", num)

original_num = 5
modify_number(original_num)
print("Number outside the function:", original_num)

Output:

Number inside the function: 10
Number outside the function: 5

Explanation

Trong hàm modify_number, biến num được nhân đôi, nhưng điều này không ảnh hưởng tới original_num bên ngoài hàm. Vì các số nguyên là bất biến, một đối tượng mới được tạo khi gán giá trị mới, và original_num vẫn không thay đổi.

3.2 Đối tượng Có thể thay đổi

Ngược lại, khi một đối tượng có thể thay đổi được truyền cho hàm, tham chiếu của nó cũng được sử dụng bên trong hàm. Do đó, các thay đổi được thực hiện trong hàm sẽ được phản ánh trong đối tượng gốc.

Ví dụ: Truyền một Danh sách Có thể thay đổi

def add_item(my_list):
    my_list.append("Added element")
    print("List inside the function:", my_list)

original_list = ["Element 1", "Element 2"]
add_item(original_list)
print("List outside the function:", original_list)

Output:

List inside the function: ['Element 1', 'Element 2', 'Added element']
List outside the function: ['Element 1', 'Element 2', 'Added element']

Explanation

Vì danh sách là có thể thay đổi, các thay đổi được thực hiện bên trong hàm add_item sẽ được phản ánh trực tiếp trong original_list bên ngoài hàm. Như vậy, các đối tượng có thể thay đổi được chia sẻ giữa bên trong và bên ngoài hàm, nên các sửa đổi trong hàm cũng ảnh hưởng tới người gọi.

Ad

4. Lưu ý và Biện pháp đối phó khi Truyền theo Tham chiếu

Việc truyền các đối tượng có thể thay đổi vào hàm trong Python có thể dẫn đến hành vi không mong muốn. Dưới đây là một số biện pháp phòng ngừa để tránh các vấn đề này.

4.1 Sử dụng bản sao của đối tượng

Nếu bạn muốn sửa đổi một đối tượng bên trong hàm, bạn có thể tránh ảnh hưởng đến đối tượng gốc bằng cách sao chép nó trước khi thực hiện thay đổi. Trong Python, bạn có thể dùng mô-đun copy để tạo bản sao nông (copy.copy) hoặc bản sao sâu (copy.deepcopy).

Ví dụ: Sử dụng Deep Copy để tránh vấn đề với đối tượng có thể thay đổi

import copy

def add_item(my_list):
    my_list_copy = copy.deepcopy(my_list)
    my_list_copy.append("Added element")
    print("List inside the function (copy):", my_list_copy)

original_list = ["Element 1", "Element 2"]
add_item(original_list)
print("List outside the function:", original_list)

Kết quả:

List inside the function (copy): ['Element 1', 'Element 2', 'Added element']
List outside the function: ['Element 1', 'Element 2']

Giải thích
Bằng cách sử dụng deepcopy, một đối tượng mới được tạo ra bên trong hàm, để lại danh sách gốc không bị ảnh hưởng. Điều này cho phép các thay đổi độc lập bên trong và bên ngoài hàm.

4.2 Sử dụng None cho các đối số mặc định

Tránh đặt các đối tượng có thể thay đổi làm đối số mặc định của hàm; nên dùng None làm giá trị mặc định và tạo một đối tượng mới bên trong hàm.

Ví dụ: Cách an toàn để đặt đối số mặc định là None

def add_item(my_list=None):
    if my_list is None:
        my_list = []
    my_list.append("New element")
    print("List inside the function:", my_list)
    return my_list

# When no list is passed
result = add_item()
print("Returned list:", result)

# When a list is passed
existing_list = ["Element 1", "Element 2"]
result = add_item(existing_list)
print("Returned list:", result)
print("Original list:", existing_list)

Kết quả:

List inside the function: ['New element']
Returned list: ['New element']
List inside the function: ['Element 1', 'Element 2', 'New element']
Returned list: ['Element 1', 'Element 2', 'New element']
Original list: ['Element 1', 'Element 2', 'New element']

Giải thích
Bằng cách sử dụng đối số mặc định None và tạo một danh sách mới bên trong hàm, bạn có thể ngăn danh sách bên ngoài bị sửa đổi một cách bất ngờ. Đặt None làm đối số mặc định làm rõ việc tạo danh sách mới hay sử dụng danh sách đã được cung cấp.

Ad

5. Ví dụ thực tế: Mã để hiểu sâu hơn về Pass-by-Reference

Hãy xem lại những gì chúng ta đã học qua một ví dụ thực tế.

Ví dụ: Thay đổi khóa và giá trị của một Dictionary

Dictionary là các đối tượng có thể thay đổi, vì vậy các thao tác thực hiện bên trong hàm sẽ ảnh hưởng đến dictionary bên ngoài hàm. Ví dụ dưới đây minh họa hành vi khi thay đổi khóa và giá trị của một dictionary.

def modify_dict(my_dict):
    my_dict["new key"] = "new value"
    print("Dictionary inside the function:", my_dict)

original_dict = {"key1": "value1", "key2": "value2"}
modify_dict(original_dict)
print("Dictionary outside the function:", original_dict)

Kết quả:

Dictionary inside the function: {'key1': 'value1', 'key2': 'value2', 'new key': 'new value'}
Dictionary outside the function: {'key1': 'value1', 'key2': 'value2', 'new key': 'new value'}

Giải thích
Vì dictionary có thể thay đổi, các bổ sung được thực hiện bên trong hàm cũng sẽ được phản ánh trong original_dict bên ngoài hàm. Giống như list, dictionary có thể ảnh hưởng đến người gọi vì chúng được truyền theo tham chiếu.

Ad

6. Tóm tắt

“Pass-by-reference” trong Python hoạt động khác nhau tùy thuộc vào đặc tính của đối tượng, vì vậy việc hiểu nó là cần thiết để nâng cao độ tin cậy của mã. Đặc biệt, để tránh hành vi không mong muốn khi truyền tham chiếu tới các đối tượng có thể thay đổi, bạn nên sử dụng sao chép và đặt đối số mặc định là None.

Các điểm tóm tắt

  • Sự khác biệt giữa truyền giá trị và truyền tham chiếu : Trong Python, việc các thay đổi bên trong hàm có ảnh hưởng đến biến gốc hay không phụ thuộc vào tính chất của đối tượng.
  • Bất biến vs. có thể thay đổi : Các đối tượng bất biến trong Python không phản ánh các thay đổi được thực hiện bên trong hàm, trong khi các đối tượng có thể thay đổi được chia sẻ giữa bên trong và bên ngoài hàm thông qua việc truyền tham chiếu.
  • Chiến lược giảm thiểu : Đối với các đối tượng có thể thay đổi, các phương pháp như sử dụng deepcopy từ mô-đun copy, hoặc đặt giá trị mặc định của đối số là None, là hiệu quả để tránh hành vi không mong muốn.

Tôi hy vọng bài viết này đã làm sâu sắc thêm hiểu biết của bạn về việc truyền tham chiếu trong Python. Hãy sử dụng kiến thức này để triển khai mã hiệu quả, ít lỗi hơn.

Ad
年収訴求