Khung dữ liệu pandas python, nó là giá trị truyền qua giá trị hay tham chiếu truyền qua


84

Nếu tôi chuyển một khung dữ liệu cho một hàm và sửa đổi nó bên trong hàm, thì nó là giá trị truyền theo giá trị hay chuyển theo tham chiếu?

Tôi chạy đoạn mã sau

a = pd.DataFrame({'a':[1,2], 'b':[3,4]})
def letgo(df):
    df = df.drop('b',axis=1)
letgo(a)

giá trị của akhông thay đổi sau khi gọi hàm. Nó có nghĩa là nó là giá trị vượt qua?

Tôi cũng đã thử những thứ sau

xx = np.array([[1,2], [3,4]])
def letgo2(x):
    x[1,1] = 100
def letgo3(x):
    x = np.array([[3,3],[3,3]])

Hóa ra letgo2()có thay đổi xxletgo3()không. Tại sao nó như thế này?


Câu trả lời:


90

Câu trả lời ngắn gọn là, Python luôn luôn chuyển theo giá trị, nhưng mọi biến Python thực sự là một con trỏ tới một số đối tượng, vì vậy đôi khi nó trông giống như tham chiếu chuyển qua.

Trong Python, mọi đối tượng đều có thể thay đổi hoặc không thể thay đổi. ví dụ: danh sách, dicts, mô-đun và khung dữ liệu Pandas có thể thay đổi được và int, chuỗi và bộ giá trị là không thể thay đổi. Các đối tượng có thể thay đổi có thể được thay đổi bên trong (ví dụ: thêm một phần tử vào danh sách), nhưng các đối tượng không thể thay đổi thì không thể.

Như tôi đã nói ở phần đầu, bạn có thể coi mọi biến Python như một con trỏ đến một đối tượng. Khi bạn truyền một biến cho một hàm, biến (con trỏ) trong hàm luôn là bản sao của biến (con trỏ) đã được chuyển vào. Vì vậy, nếu bạn gán một cái gì đó mới cho biến nội bộ, tất cả những gì bạn đang làm là thay đổi biến cục bộ để trỏ đến một đối tượng khác. Điều này không làm thay đổi (đột biến) đối tượng ban đầu mà biến trỏ đến, cũng không làm cho biến bên ngoài trỏ đến đối tượng mới. Tại thời điểm này, biến bên ngoài vẫn trỏ đến đối tượng ban đầu, nhưng biến bên trong trỏ đến đối tượng mới.

Nếu bạn muốn thay đổi đối tượng ban đầu (chỉ có thể với các kiểu dữ liệu có thể thay đổi), bạn phải làm điều gì đó thay đổi đối tượng mà không chỉ định một giá trị hoàn toàn mới cho biến cục bộ. Đây là lý do tại sao letgo()letgo3()để vật phẩm bên ngoài không thay đổi, nhưng letgo2()thay đổi nó.

Như @ursan đã chỉ ra, nếu letgo()sử dụng thứ gì đó như thế này thay thế, thì nó sẽ thay đổi (biến đổi) đối tượng ban đầu dftrỏ đến, điều này sẽ thay đổi giá trị được nhìn thấy qua abiến toàn cục:

def letgo(df):
    df.drop('b', axis=1, inplace=True)

a = pd.DataFrame({'a':[1,2], 'b':[3,4]})
letgo(a)  # will alter a

Trong một số trường hợp, bạn hoàn toàn có thể làm rỗng biến ban đầu và điền vào nó bằng dữ liệu mới mà không thực sự thực hiện phép gán trực tiếp, ví dụ: điều này sẽ thay đổi đối tượng ban đầu vtrỏ đến, điều này sẽ thay đổi dữ liệu được thấy khi bạn sử dụng vsau này:

def letgo3(x):
    x[:] = np.array([[3,3],[3,3]])

v = np.empty((2, 2))
letgo3(v)   # will alter v

Lưu ý rằng tôi không chỉ định một cái gì đó trực tiếp cho x; Tôi đang chỉ định một cái gì đó cho toàn bộ phạm vi nội bộ của x.

Nếu bạn nhất thiết phải tạo một đối tượng hoàn toàn mới và làm cho nó hiển thị ra bên ngoài (trường hợp này đôi khi xảy ra với gấu trúc), bạn có hai lựa chọn. Tùy chọn 'sạch' sẽ chỉ để trả về đối tượng mới, ví dụ:

def letgo(df):
    df = df.drop('b',axis=1)
    return df

a = pd.DataFrame({'a':[1,2], 'b':[3,4]})
a = letgo(a)

Một tùy chọn khác là tiếp cận bên ngoài hàm của bạn và trực tiếp thay đổi một biến toàn cục. Điều này thay đổi ađể trỏ đến một đối tượng mới và bất kỳ hàm nào tham chiếu đến asau đó sẽ thấy đối tượng mới đó:

def letgo():
    global a
    a = a.drop('b',axis=1)

a = pd.DataFrame({'a':[1,2], 'b':[3,4]})
letgo()   # will alter a!

Thay đổi trực tiếp các biến toàn cục thường là một ý tưởng tồi, bởi vì bất kỳ ai đọc mã của bạn đều sẽ gặp khó khăn trong việc tìm hiểu xem ađã thay đổi như thế nào . (Tôi thường sử dụng các biến toàn cục cho các tham số dùng chung được sử dụng bởi nhiều hàm trong một tập lệnh, nhưng tôi không để chúng thay đổi các biến toàn cục đó.)


7

Câu hỏi không phải là PBV so với PBR. Những cái tên này chỉ gây ra sự nhầm lẫn trong một ngôn ngữ như Python; chúng được phát minh cho các ngôn ngữ hoạt động như C hoặc như Fortran (là ngôn ngữ PBV và PBR tinh túy). Đúng, nhưng không khai sáng, rằng Python luôn đi theo giá trị. Câu hỏi ở đây là liệu bản thân giá trị có bị đột biến hay không hay liệu bạn có nhận được giá trị mới hay không. Gấu trúc thường sai ở phía sau của gấu trúc.

http://nedbatchelder.com/text/names.html giải thích rất rõ hệ thống tên của Python là gì.


1
Ngữ nghĩa của việc truyền và gán trong Python hoàn toàn giống như trong Java và những điều bạn nói cũng có thể được áp dụng cho Java. Tuy nhiên, trên StackOverflow và các nơi khác trên Internet, mọi người dường như thấy thật "ngộ" khi ấn tượng với bạn rằng Java luôn có giá trị vượt trội bất cứ khi nào vấn đề này xuất hiện.
newacct

7

Để thêm vào câu trả lời của @Mike Graham, người đã chỉ ra một bài đọc rất hay:

Trong trường hợp của bạn, điều quan trọng cần nhớ là sự khác biệt giữa têngiá trị . a, df, xx, x, Là tất cả tên , nhưng họ đề cập đến sự giống và khác nhau giá trị tại các điểm khác nhau của ví dụ của bạn:

  • Trong ví dụ đầu tiên, letgo rebinds df giá trị khác, vì df.droplợi nhuận mới DataFrame, trừ khi bạn thiết lập các tham số inplace = True( xem doc ). Điều đó có nghĩa là tên df(cục bộ của letgohàm), đang tham chiếu đến giá trị của a, hiện đang tham chiếu đến một giá trị mới, ở đây là df.dropgiá trị trả về. Giá trị ađược đề cập đến vẫn tồn tại và không thay đổi.

  • Trong ví dụ thứ hai, các letgo2 đột biến x , mà không gắn nó lại, đó là lý do tại sao xxđược sửa đổi bởi letgo2. Không giống như ví dụ trước, ở đây tên địa phương xluôn đề cập đến giá trị mà tên xxđang đề cập đến và thay đổi giá trị đó tại chỗ , đó là lý do tại sao giá trị xxđược đề cập đến đã thay đổi.

  • Trong ví dụ thứ ba, letgo3 rebinds x đến một mới np.array. Điều đó khiến tên x, từ địa phương đến letgo3và trước đây đề cập đến giá trị của xx, bây giờ đề cập đến một giá trị khác, giá trị mới np.array. Giá trị xxđược đề cập đến không thay đổi.


3

Python không truyền theo giá trị cũng không chuyển theo tham chiếu. Nó được chuyển qua phân công.

Tham khảo hỗ trợ, Câu hỏi thường gặp về Python: https://docs.python.org/3/faq/programming.html#how-do-i-write-a- Chức năng-with-output-parameters-call-by-reference

IOW:

  1. Nếu bạn truyền một giá trị không thay đổi, các thay đổi đối với nó không thay đổi giá trị của nó trong trình gọi - bởi vì bạn đang gắn tên lại với một đối tượng mới.
  2. Nếu bạn chuyển một giá trị có thể thay đổi, các thay đổi được thực hiện trong hàm được gọi, cũng sẽ thay đổi giá trị trong trình gọi, miễn là bạn không gắn tên đó vào một đối tượng mới. Nếu bạn gán lại biến, tạo một đối tượng mới, thay đổi đó và các thay đổi tiếp theo đối với tên sẽ không được nhìn thấy trong trình gọi.

Vì vậy, nếu bạn chuyển một danh sách và thay đổi giá trị thứ 0 của nó, thì sự thay đổi đó được nhìn thấy trong cả người được gọi và người gọi. Nhưng nếu bạn gán lại danh sách bằng một danh sách mới, thay đổi này sẽ bị mất. Nhưng nếu bạn cắt danh sách và thay thế danh sách đó bằng một danh sách mới, thay đổi đó sẽ được nhìn thấy trong cả người được gọi và người gọi.

VÍ DỤ:

def change_it(list_):
    # This change would be seen in the caller if we left it alone
    list_[0] = 28

    # This change is also seen in the caller, and replaces the above
    # change
    list_[:] = [1, 2]

    # This change is not seen in the caller.
    # If this were pass by reference, this change too would be seen in
    # caller.
    list_ = [3, 4]

thing = [10, 20]
change_it(thing)
# here, thing is [1, 2]

Nếu bạn là một người hâm mộ C, bạn có thể nghĩ điều này giống như việc chuyển một con trỏ theo giá trị - không phải là một con trỏ tới một con trỏ đến một giá trị, chỉ là một con trỏ tới một giá trị.

HTH.


0

Đây là tài liệu để thả:

Trả lại đối tượng mới với các nhãn trong trục được yêu cầu đã bị xóa.

Vì vậy, một khung dữ liệu mới được tạo. Bản chính không thay đổi.

Nhưng đối với tất cả các đối tượng trong python, khung dữ liệu được chuyển đến hàm bằng tham chiếu.


nhưng tôi đã gán nó vào dfbên trong hàm, không có nghĩa là giá trị được tham chiếu đã được thay đổi thành đối tượng mới?
nos

Việc gán cho một tên cục bộ sẽ không bao giờ thay đổi đối tượng mà một tên được liên kết trong một phạm vi khác.
Mike Graham

0

bạn cần tạo "a" toàn cục khi bắt đầu hàm, nếu không nó là biến cục bộ và không thay đổi "a" trong mã chính.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.