[ATBS2nd]Chương 1 - Python cơ bản - Phần 15

Chào các bạn,
Chúng ta tiếp tục với danh sách trong Python.
Tham chiếu
Như bạn đã thấy, các biến số lưu trữ các chuỗi và giá trị số nguyên. Tuy nhiên, lời giải thích này là sự đơn giản hóa những gì Python thực sự đang làm. Về mặt kỹ thuật, các biến đang lưu trữ các tham chiếu đến các vị trí bộ nhớ máy tính nơi các giá trị được lưu trữ. Nhập thông tin sau vào command line:

>>> spam = 42
>>> cheese = spam
>>> spam = 100
>>> spam
100
>>> cheese
42

Khi bạn gán 42 cho biến spam, bạn thực sự đang tạo giá trị 42 trong bộ nhớ máy tính và lưu trữ một tham chiếu đến nó trong biến spam. Khi bạn sao chép giá trị trong spam và gán nó cho biến cheese, bạn thực sự đang sao chép tham chiếu. Cả hai biến spam và cheese đều đề cập đến giá trị 42 trong bộ nhớ máy tính. Khi bạn sau đó thay đổi giá trị trong spam thành 100, bạn sẽ tạo ra một giá trị 100 mới và lưu trữ một tham chiếu đến spam. Điều này không ảnh hưởng đến giá trị trong cheese. Số nguyên là những giá trị bất biến khiến nó không thay đổi; thay đổi biến spam thực sự làm cho nó tham chiếu đến một giá trị hoàn toàn khác trong bộ nhớ.
Nhưng danh sách không lồng hoạt động theo cách này, bởi vì giá trị danh sách có thể thay đổi; đó là danh sách có thể thay đổi dưới đây là một số mã sẽ làm cho sự khác biệt này dễ hiểu hơn. Nhập đoạn mã sau vào command line:

>>> spam = [0, 1, 2, 3, 4, 5]
>>> cheese = spam # The reference is being copied, not the list.
>>> cheese[1] = 'Hello!' # This changes the list value.
>>> spam
[0, 'Hello!', 2, 3, 4, 5]
>>> cheese # The cheese variable refers to the same list.
[0, 'Hello!', 2, 3, 4, 5]

Điều này có thể trông kỳ lạ với bạn. Mã chỉ thay đổi vào danh sách cheese, nhưng dường như cả danh sách cheese và spam đã thay đổi.
Khi bạn tạo danh sách, bạn chỉ định tham chiếu cho nó trong biến spam. Nhưng dòng tiếp theo chỉ sao chép danh sách tham chiếu trong spam sang cheese, chứ không phải giá trị danh sách. Điều này có nghĩa là các giá trị được lưu trữ trong spam và cheese hiện cả hai đều đề cập đến cùng một danh sách. Chỉ có một danh sách cơ bản vì bản thân danh sách chưa bao giờ được sao chép. Vì vậy, khi bạn sửa đổi thành phần đầu tiên của cheese, bạn đang sửa đổi cùng một danh sách mà spam đề cập đến. Hãy nhớ rằng các biến giống như các hộp chứa giá trị. Các số liệu trước trong chương này cho thấy rằng các danh sách trong các hộp không chính xác, bởi vì các biến danh sách không thực sự có chứa các danh sách mà chúng chứa các tham chiếu đến các danh sách. (Các tham chiếu này sẽ có số ID mà Python sử dụng nội bộ, nhưng bạn có thể bỏ qua chúng.) Sử dụng các hộp làm ẩn dụ cho các biến, Hình dưới đây cho thấy điều gì xảy ra khi danh sách được gán cho biến spam.
b29
Sau đó, trong hình tiếp theo, tham chiếu trong spam được sao chép vào cheese. Chỉ một tham chiếu mới được tạo và lưu trữ trong cheese, không phải là một danh sách mới. Lưu ý cách cả hai tham chiếu tham chiếu cùng một danh sách.
b30
Khi bạn thay đổi danh sách mà cheese đề cập đến, danh sách mà spam đề cập đến cũng bị thay đổi, bởi vì cả cheese và spam đều đề cập đến cùng một danh sách. Bạn có thể thấy điều này trong hình dưới đây.
b31
Mặc dù các biến Python về mặt kỹ thuật có chứa các tham chiếu đến các giá trị, mọi người thường tình cờ nói rằng biến đó chứa giá trị.
Sự phân biệt và hàm id()
Bạn có thể tự hỏi tại sao hành vi kỳ lạ với danh sách có thể thay đổi trong phần trước không xảy ra với các giá trị bất biến như số nguyên hoặc chuỗi. Chúng ta có thể sử dụng hàm Python id() để hiểu điều này. Tất cả các giá trị trong Python có một danh tính duy nhất có thể thu được bằng hàm id (). Nhập thông tin sau vào vỏ command line:

>>> id('Howdy') # The returned number will be different on your machine.
44491136

Khi Python chạy id (‘Howdy’), nó sẽ tạo ra chuỗi ‘Howdy’ trong bộ nhớ máy tính. Địa chỉ bộ nhớ số nơi chuỗi được lưu trữ được trả về bởi hàm id (). Python chọn địa chỉ này dựa trên các byte bộ nhớ trống trên máy tính của bạn tại thời điểm đó, do đó, nó sẽ khác nhau mỗi khi bạn chạy mã này.
Giống như tất cả các chuỗi, ‘Howdy’ là bất biến và không thể thay đổi. Nếu bạn thay đổi chuỗi trong một biến, một đối tượng chuỗi mới sẽ được tạo ở một vị trí khác trong bộ nhớ và biến đó đề cập đến chuỗi mới này. Ví dụ: nhập thông tin sau vào command line và xem cách nhận dạng của chuỗi được gọi bằng bacon thay đổi:

>>> bacon = 'Hello'
>>> id(bacon)
44491136
>>> bacon += ' world!' # A new string is made from 'Hello' and ' world!'.
>>> id(bacon) # bacon now refers to a completely different string.
44609712

Tuy nhiên, danh sách có thể được sửa đổi bởi vì chúng là các đối tượng có thể thay đổi. Phương thức append () không tạo ra một đối tượng danh sách mới; nó thay đổi đối tượng danh sách hiện có. Chúng ta gọi đây là việc sửa đổi đối tượng tại chỗ.

>>> eggs = ['cat', 'dog'] # This creates a new list.
>>> id(eggs)
35152584
>>> eggs.append('moose') # append() modifies the list "in place".
>>> id(eggs) # eggs still refers to the same list as before.
35152584
>>> eggs = ['bat', 'rat', 'cow'] # This creates a new list, which has a new
identity.
>>> id(eggs) # eggs now refers to a completely different list.
44409800

Nếu hai biến tham chiếu đến cùng một danh sách (như spam và cheese trong phần trước) và chính giá trị danh sách thay đổi, cả hai biến đều bị ảnh hưởng vì cả hai đều tham chiếu đến cùng một danh sách. Các phương thức append (), extend (), remove (), sort (), reverse () và các phương thức danh sách khác sửa đổi danh sách của chúng tại chỗ.
Trình thu gom rác tự động Python sẽ xóa mọi giá trị không được tham chiếu bởi bất kỳ biến nào để giải phóng bộ nhớ. Bạn không cần phải lo lắng về cách thức hoạt động của trình thu gom rác, đây là một điều tốt: quản lý bộ nhớ thủ công trong các ngôn ngữ lập trình khác là một lỗi phổ biến.
Xử lý tham chiếu
Tham chiếu đặc biệt quan trọng để hiểu cách các đối số được truyền vào các hàm. Khi một hàm được gọi, các giá trị của các đối số được sao chép vào các biến tham số. Đối với các danh sách (và từ điển, mà tôi sẽ mô tả trong phần tiếp theo), điều này có nghĩa là một bản sao của tham chiếu được sử dụng cho tham số. Để xem hiệu quả của việc này, hãy mở một cửa sổ soạn thảo file mới, nhập mã sau đây và lưu nó dưới dạng passReference.py:

def eggs(someParameter):
	someParameter.append('Hello')
spam = [1, 2, 3]
eggs(spam)
print(spam)

Lưu ý rằng khi gọi eggs(), giá trị trả về không được sử dụng để gán giá trị mới cho spam. Thay vào đó, nó sửa đổi danh sách tại chỗ, trực tiếp. Khi chạy, chương trình này tạo ra đầu ra sau:
`

[1, 2, 3, ‘Hello’]

`
Mặc dù spam và someParameter chứa các tham chiếu riêng biệt, cả hai đều tham chiếu đến cùng một danh sách. Đây là lý do tại sao lệnh gọi append(‘Hello’) bên trong hàm ảnh hưởng đến danh sách ngay cả sau khi lệnh gọi hàm được trả về.
Hãy ghi nhớ hành vi này: quên đi rằng Python xử lý các biến danh sách và từ điển theo cách này có thể dẫn đến các lỗi khó hiểu.
Hàm copy() và deepcopy()
Mặc dù chuyển qua các tham chiếu thường là cách dễ nhất để xử lý danh sách và từ điển, nếu chức năng sửa đổi danh sách hoặc từ điển được thông qua, bạn có thể không muốn những thay đổi này trong danh sách gốc hoặc giá trị từ điển. Đối với điều này, Python cung cấp một mô-đun có tên là copy cung cấp cả hai hàm copy () và deepcopy (). Cái đầu tiên trong số này, copy.copy (), có thể được sử dụng để tạo một bản sao trùng lặp của một giá trị có thể thay đổi như danh sách hoặc từ điển, không chỉ là bản sao của một tham chiếu. Nhập thông tin sau vào command line

>>> import copy
>>> spam = ['A', 'B', 'C', 'D']
>>> id(spam)
44684232
>>> cheese = copy.copy(spam)
>>> id(cheese) # cheese is a different list with different identity.
44685832
>>> cheese[1] = 42
>>> spam
['A', 'B', 'C', 'D']
>>> cheese
['A', 42, 'C', 'D']

Bây giờ các biến spam và cheese đề cập đến các danh sách riêng biệt, đó là lý do tại sao chỉ có danh sách trong cheese được sửa đổi khi bạn gán 42. Như bạn có thể thấy trong hình dưới đây, các số ID tham chiếu không còn giống nhau cho cả hai các biến vì các biến tham chiếu đến danh sách độc lập.


Nếu danh sách bạn cần sao chép chứa danh sách, thì hãy sử dụng hàm copy.deepcopy () thay vì copy.copy (). Hàm deepcopy () cũng sẽ sao chép các danh sách bên trong này.
Người dịch: Hungdh

3 Likes