python pandas dataframe, czy jest to przekazywanie przez wartość, czy przekazywanie przez odniesienie

88

Jeśli przekażę ramkę danych do funkcji i zmodyfikuję ją wewnątrz funkcji, czy jest to przekazanie przez wartość czy przekazanie przez odwołanie?

Uruchamiam następujący kod

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

wartość anie zmienia się po wywołaniu funkcji. Czy to oznacza, że ​​jest to wartość przekazana?

Wypróbowałem również następujące

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

Okazuje się, letgo2()że się zmienia, xxa letgo3()nie. Dlaczego tak jest?

nr
źródło

Odpowiedzi:

95

Krótka odpowiedź brzmi: Python zawsze przekazuje wartość, ale każda zmienna Pythona jest w rzeczywistości wskaźnikiem do jakiegoś obiektu, więc czasami wygląda jak przekazywanie przez odniesienie.

W Pythonie każdy obiekt jest zmienny lub niezmienny. np. listy, dykty, moduły i ramki danych Pandas są modyfikowalne, a wartości int, stringi i krotki są niezmienne. Obiekty, które można modyfikować, można zmieniać wewnętrznie (np. Dodawać element do listy), ale obiektów niemodyfikowalnych nie można.

Jak powiedziałem na początku, każdą zmienną Pythona można traktować jako wskaźnik do obiektu. Kiedy przekazujesz zmienną do funkcji, zmienna (wskaźnik) w funkcji jest zawsze kopią przekazanej zmiennej (wskaźnika). Jeśli więc przypiszesz coś nowego do zmiennej wewnętrznej, wszystko co robisz to zmiana zmienna lokalna, aby wskazywała na inny obiekt. Nie zmienia to (nie mutuje) oryginalnego obiektu, na który wskazywała zmienna, ani nie powoduje, że zmienna zewnętrzna wskazuje na nowy obiekt. W tym momencie zmienna zewnętrzna nadal wskazuje na oryginalny obiekt, ale zmienna wewnętrzna wskazuje na nowy obiekt.

Jeśli chcesz zmienić oryginalny obiekt (możliwe tylko w przypadku zmiennych typów danych), musisz zrobić coś, co zmieni obiekt bez przypisywania zupełnie nowej wartości do zmiennej lokalnej. Dlatego letgo()i letgo3()pozostawić element zewnętrzny niezmienione, ale letgo2()zmienia go.

Jak zauważył @ursan, gdyby letgo()zamiast tego użył czegoś takiego, zmieniłby (zmutował) oryginalny obiekt, na który dfwskazuje, co zmieniłoby wartość widzianą przez azmienną globalną :

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

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

W niektórych przypadkach możesz całkowicie wydrążyć oryginalną zmienną i uzupełnić ją nowymi danymi, bez wykonywania bezpośredniego przypisania, np. Zmieni to oryginalny obiekt, na który vwskazuje, co zmieni dane widoczne, gdy użyjesz vpóźniej:

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

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

Zauważ, że nie przypisuję czegoś bezpośrednio do x; Przypisuję coś do całego wewnętrznego zakresu x.

Jeśli absolutnie musisz stworzyć zupełnie nowy obiekt i uczynić go widocznym na zewnątrz (co czasami ma miejsce w przypadku pand), masz dwie możliwości. Opcją „wyczyść” byłoby po prostu zwrócenie nowego obiektu, np.

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

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

Inną opcją byłoby wyjście poza swoją funkcję i bezpośrednia zmiana zmiennej globalnej. Zmieni się to, aaby wskazać nowy obiekt, a każda funkcja, do której odwołuje się apóźniej, zobaczy ten nowy obiekt:

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

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

Bezpośrednia zmiana zmiennych globalnych jest zwykle złym pomysłem, ponieważ każdy, kto czyta Twój kod, będzie miał trudności z ustaleniem, w jaki sposób azostał zmieniony. (Generalnie używam zmiennych globalnych dla parametrów współdzielonych używanych przez wiele funkcji w skrypcie, ale nie pozwalam im zmieniać tych zmiennych globalnych).

Matthias Fripp
źródło
8

Aby dodać do odpowiedzi @Mike Graham, która wskazała na bardzo dobrą lekturę:

W twoim przypadku należy pamiętać o różnicy między nazwami a wartościami . a, df, xx, x, Są wszystkie nazwiska , ale odnoszą się one do tych samych lub różnych wartości w różnych punktach swoich przykładach:

  • W pierwszym przykładzie letgo dokonuje ponownego wiązania df na inną wartość, ponieważ df.dropzwraca nową wartość, DataFramechyba że ustawisz argument inplace = True( zobacz dokumentację ). Oznacza to, że nazwa df(lokalna dla letgofunkcji), która odnosiła się do wartości funkcji a, odnosi się teraz do nowej wartości, tutaj df.dropwartość zwracana. Wartość, ado której się odnosi, nadal istnieje i nie uległa zmianie.

  • W drugim przykładzie letgo2 mutuje x bez ponownego wiązania, dlatego xxjest modyfikowany przez letgo2. W przeciwieństwie do poprzedniego przykładu, tutaj nazwa lokalna xzawsze odnosi się do wartości, do której odwołuje się nazwa xx, i zmienia tę wartość w miejscu , dlatego ta wartość xxsię zmieniła.

  • W trzecim przykładzie ponownie letgo3 wiąże x się z nowym np.array. To powoduje, że nazwa xlokalna letgo3i poprzednio odwołująca się do wartości xx, teraz odwołuje się do innej wartości, new np.array. Wartość, xxdo której się odnosi, nie uległa zmianie.

ursan
źródło
7

Pytanie nie dotyczy PBV kontra PBR. Nazwy te powodują zamieszanie tylko w języku takim jak Python; zostały wynalezione dla języków, które działają jak C lub jak Fortran (jako kwintesencja języków PBV i PBR). To prawda, ale nie pouczające, że Python zawsze przekazuje wartość. Pytanie brzmi, czy sama wartość jest zmutowana, czy też otrzymujesz nową wartość. Pandy zwykle błądzą po stronie tego ostatniego.

http://nedbatchelder.com/text/names.html wyjaśnia bardzo dobrze, czym jest system nazw w Pythonie.

Mike Graham
źródło
1
Semantyka przekazywania i przypisywania w Pythonie jest dokładnie taka sama jak w Javie, a te same rzeczy, które mówisz, można w równym stopniu zastosować do Javy. Jednak w StackOverflow i innych miejscach w Internecie ludzie najwyraźniej uważają za „pouczające” przekonanie cię, że Java jest zawsze przekazywana za wartością, gdy pojawia się ten problem.
newacct
3

Python nie jest przekazywany przez wartość ani przez odniesienie. To przechodzi przez przydział.

Dokumentacja pomocnicza, Python FAQ: https://docs.python.org/3/faq/programming.html#how-do-i-write-a-function-with-output-parameters-call-by-reference

IOW:

  1. Jeśli przekażesz niezmienną wartość, zmiany w niej nie zmieniają jej wartości w obiekcie wywołującym - ponieważ ponownie wiążesz nazwę z nowym obiektem.
  2. Jeśli przekazujesz zmienną wartość, zmiany dokonane w wywołanej funkcji, zmienią również wartość w obiekcie wywołującym, o ile nie powiążesz tej nazwy z nowym obiektem. Jeśli zmienisz przypisanie zmiennej, tworząc nowy obiekt, ta zmiana i kolejne zmiany nazwy nie będą widoczne w obiekcie wywołującym.

Więc jeśli przekażesz listę i zmienisz jej zerową wartość, ta zmiana będzie widoczna zarówno w wywoływanym, jak i dzwoniącym. Ale jeśli zmienisz przypisanie listy z nową listą, ta zmiana zostanie utracona. Ale jeśli pokroić listę i zastąpi że z nowej listy, że zmiana jest postrzegana zarówno w zadzwonił i rozmówcy.

NA PRZYKŁAD:

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]

Jeśli jesteś fanem C, możesz myśleć o tym jako o przekazywaniu wskaźnika po wartości - nie wskaźnika do wskaźnika do wartości, tylko wskaźnik do wartości.

HTH.

dstromberg
źródło
0

Oto dokument do upuszczenia:

Zwróć nowy obiekt z usuniętymi etykietami w żądanej osi.

Powstaje więc nowa ramka danych. Oryginał się nie zmienił.

Ale tak jak w przypadku wszystkich obiektów w Pythonie, ramka danych jest przekazywana do funkcji przez odniesienie.

Israel Unterman
źródło
ale przypisałem go do dfwewnątrz funkcji, czy nie oznacza to, że wskazana wartość została zmieniona na nowy obiekt?
nr
Przypisanie do nazwy lokalnej nigdy nie zmieni obiektu, do którego jest przypisana nazwa w innym zakresie.
Mike Graham
0

musisz ustawić „a” jako globalne na początku funkcji, w przeciwnym razie jest to zmienna lokalna i nie zmienia „a” w głównym kodzie.

zosan
źródło