List biến đổi khi qua hàm?

Em chào mọi người,
Như trong C/C++ (em chưa học) thì có nghe mọi người nói qua pointer, thì không biết vấn đề em gặp có phải thuộc vấn đề này không? Hay là thuộc về nội dung pass by value hay pass by reference.
Cụ thể em lấy ví dụ của hai bài code như sau, 1 bài thì sau khi em thực hiện giá trị list ban đầu bị thay đổi, 1 cái thì không. Tại sao lại có sự khác nhau như trên ạ?
Và có cách nào để khi code em biết chắc chắn là nó có thay đổi hay không không!
Em cảm ơn,

# List as argument - Modifying argument

def doubleList(a_listNum):
    nLen = len(a_listNum)

    for i in range(0, nLen):
        a_listNum[i] = 2 * a_listNum[i]

# Call
listNum = [1, 2, 3, 4]
doubleList(listNum)
print(listNum)
[2, 4, 6, 8]
def clearList(a_listNum):
    a_listNum = []
    print(a_listNum)

listNum = [1, 2, 3, 4]
clearList(listNum)
print(listNum)
>> %Run sffs.py
[]
[1, 2, 3, 4]

Thực ra khi bạn khai báo a_listNum = [] thì giống như bạn khai báo 1 biến local có tên trùng tên với argument a_listNum mà bạn đưa vào. Nghĩa là 2 chúng nó là 2 object hoàn toàn khác nhau.

Còn về vấn đề pass by value hay pass by reference. thì đơn giản như sau:

  • Immutable type (Simple build-in type) pass by value: int, long, float
  • Mutable types pass by reference: list, class
4 Likes

Vậy mình làm sao biết là trường hợp em Double list nó lại không tạo ra 1 biến local khác ấy anh?
Em vẫn bị lấn cấn hoài đoạn này, kiểu em hay debug để xem rồi chỉnh nhưng vẫn không hiểu sao nó sẽ modify hay không.
Và tùy thuộc vào cách em làm thì nó sẽ pass by value hay reference ạ anh? Trong Python em thấy không được đề cập vấn này sâu lắm, trong C++ thì nó chính là pointer ấy hả anh

Mình không học python, nhưng cách nghĩ của mình khác với bạn trên. Ví dụ đầu là List được pass by reference là đúng, không có gì phải nói.
Còn ví dụ thứ 2, thì chỗ

a_listNum = []

không phải tạo biến local mới, mà là thay đổi vùng nhớ trỏ tới. Thay vì tham số a_listNum trỏ tới vùng nhớ của list cũ, thì nó lại bị đổi sang vùng nhớ mới (rỗng).
Do đó khi ra khỏi hàm thì vùng nhớ trước đây không bị ảnh hưởng.

2 Likes

Việc bạn sử dụng gán bằng rỗng cho mục đích clearList là hoàn toàn không có tác dụng. Đây là cách làm sai. Các cách làm đúng: https://stackoverflow.com/questions/850795/different-ways-of-clearing-lists

Python dự vào kiểu dữ liệu (type) được pass vào để quyết định là dùng value hay reference.
Nói cho đơn giản, nếu đưa vào foo(a) mà a là integer thì nó lấy value của a để thực hiện công việc của hàm. Còn nếu a là list thì nó xử lý trên chính list đó.

Nếu theo cách hiểu của bạn thì sau khi a_listNum được trỏ tới vùng nhớ mới (rỗng) thì sau khi thoát khỏi hàm thì cái gì trỏ tới vùng nhớ cũ? Nếu a_listNum tự động trỏ tới vùng nhớ cũ thì mọi thao tác với a_listNum nằm trong hàm đâu liên quan gì tới ngoài hàm, như vậy nó hoàn toàn độc lập với thằng ngoài rồi -> vậy mình xem nó là biến local mới đâu có gì sai đâu.

Theo python chính thống thì không có khái niệm pointer, mà họ dùng khái niệm label.

5 Likes

Để rõ hơn cho ý mà nitro2 trả lời, bạn đọc qua 2 links này:

Cái này là giải thích về cái biến local ở ví dụ 2
https://www.programiz.com/python-programming/global-local-nonlocal-variables

Còn vì sao ở ví dụ 1, cũng là chạy lung tun bên trong function, nhưng behavior của nó không giống như ở ví dụ 2, là lại bị thay đổi, bạn tham khảo linh này

4 Likes

Vậy là để quyết định yếu tố value hay reference sẽ dựa vào type hả anh?
Như anh nói em thì

  • value: int, long, float, str chẳng hạn
  • reference: list, class

Trong trường hợp em clear list thì a ở đây cũng là list nhưng sao nó lại phân vào pass by value anh? Nếu như chỉ đơn giản nhìn vào phần parameter ở phần khai hàm thì dễ hơn. Đợt trước em có test thử sự khác nhau giữa việc dùng clear() và = [] rồi nhưng vẫn không thông lắm.
Ý thứ 2 là em thấy việc anh và anh kia nói hình như không khác nhau lắm, việc trỏ đến vùng nhớ khác hay là giá trị biến mới cũng như nhau :3

Dạ rồi, để em đọc cái này và ngẫm lại rồi có gì thắc mắc anh giúp đỡ thêm ạ, em cảm ơn.

Torng link có phần này, họ nhấn mạnh

In Python, data types can be either mutable (changeable) or immutable (unchangable). And while most of the data types we’ve worked with in introductory Python are immutable (including integers, floats, strings, Booleans, and tuples), lists and dictionaries are mutable. That means a global list or dictionary can be changed even when it’s used inside of a function ,

5 Likes

Dạ vâng, cái phần integers sẽ luôn không thay đổi thì em có biết, còn kiểu em đang bị rối chỗ list có khi đổi, có khi không.
Em nghĩ chắc vấn đề nằm ở chỗ em chưa hiểu cách pass by value hay pass by reference hoạt động ra sao, hay việc trỏ đến vùng nhớ khi nào.
Có gì để em đọc link anh trước rồi coi lại.

List luôn luôn pass by reference, việc bạn gán a_List = [] đã vô tình KHAI BÁO một vùng nhớ mới. Nên nó sai. Bạn đọc kỹ lại các nội dung trên.

1 Like

Hai biến tham chiếu khác nhau mà. Biến a_listNum bên ngoài với tham số a_listNum của hàm là 2 biến riêng biệt. Trong hàm a_listNum trỏ tới vùng nhớ khác thì ảnh hưởng gì tới a_listNum bên ngoài?

2 Likes

Bạn không nói tạo biến local mới thì lại mâu thuẫn với câu này sao?

a global list or dictionary can be changed even when it’s used inside of a function vì list là mutable

nên trong ví dụ 1, function nó modify trực tiếp giá trị của list

Trong ví dụ 2, funtion tạo 1 biến local “trùng tên” với list, hết function thì tan thành mây khói

2 Likes

Hai biến ở đây một là biến listNum (mình gõ nhầm) global với tham số a_listNum. Không có biến local nào cả.
Có vẻ như hai khái niệm này bị lẫn lộn:

  • Tạo biến local mới
  • Tạo vùng nhớ mới và cho a_listNum trỏ tới nó.
2 Likes

Ý của anh muốn nói với nhím là
listNum biến global
def clearList(a_listNum) a_listNum là tham số
Quy trình hoạt động là anh nói tức là khi chạy a_listNum sẽ nhận giá trị global của listNum,sau đó trỏ đến vùng nhớ mới chứ không phải tạo biến local mới.
Em đang đọc để coi lại sao nó không modify global của list thành [] mà trỏ đến vùng nhớ khác.
Các anh bàn luận trước có gì em đọc rồi trình bày lại nội dung xem em hiểu còn sai không ^^

Local variables can have the same name as global variables (those declared outside of any function). In that case, the global variable is inaccessible from within the function

Tức ở ví dụ 2, khi bạn tạo list a_listNum và gán gía trị [] cho nó, thì khi đó bạn không thể access biến global cùng tên đó nữa. Tức đó là 1 biến hoàn toàn khác, tức mục đích “clear list” của bạn đi sai hướng

2 Likes

Mình có googling lại một hồi thì tìm được link này. Về khoản pass biến vào hàm thì python khá giống với java.

Object references are passed by value.

Nghĩa là khi truyền tham chiếu, thì tạo ra thêm 1 bản copy biến tham chiếu và đưa vào hàm. Hai biến tham chiếu này cùng trỏ vào một vùng nhớ. Do đó nếu biến a_listNum (trong hàm) trỏ tới vùng nhớ khác là [] thì tham chiếu global listNum cũng không ảnh hưởng.

https://robertheaton.com/2014/02/09/pythons-pass-by-object-reference-as-explained-by-philip-k-dic
k/ (DNH chặn từ cuối trong link)

2 Likes

Có người nhắc mình là cách đây hơn 1 tháng, bạn này hỏi câu y chang: Sự thay đổi của List trong vòng lặp ở Python

Bạn này là “cô gái đến từ hôm qua” à?

Hi vọng tháng sau bạn không hỏi câu này nữa :zipper_mouth_face:

6 Likes

Hôm đó em có lấy ví dụ trường hợp clear() và thế bằng = [] nhưng em vẫn chưa hiểu sự khác nhau ấy anh.
Như qua bài này thì nội dung em sai là ở cách em gán biến thì nó không còn là pass by reference như cách biến hoạt động nữa.
Còn như về việc pass by reference hay value thì như anh thông tin cho em ở phía trên thì list, dict auto pass by reference

83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?