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ść a
nie 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, xx
a letgo3()
nie. Dlaczego tak jest?
Odpowiedzi:
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()
iletgo3()
pozostawić element zewnętrzny niezmienione, aleletgo2()
zmienia go.Jak zauważył @ursan, gdyby
letgo()
zamiast tego użył czegoś takiego, zmieniłby (zmutował) oryginalny obiekt, na którydf
wskazuje, co zmieniłoby wartość widzianą przeza
zmienną 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
v
wskazuje, co zmieni dane widoczne, gdy użyjeszv
póź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 zakresux
.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,
a
aby wskazać nowy obiekt, a każda funkcja, do której odwołuje sięa
póź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
a
został 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).źródło
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ązaniadf
na inną wartość, ponieważdf.drop
zwraca nową wartość,DataFrame
chyba że ustawisz argumentinplace = True
( zobacz dokumentację ). Oznacza to, że nazwadf
(lokalna dlaletgo
funkcji), która odnosiła się do wartości funkcjia
, odnosi się teraz do nowej wartości, tutajdf.drop
wartość zwracana. Wartość,a
do której się odnosi, nadal istnieje i nie uległa zmianie.W drugim przykładzie
letgo2
mutujex
bez ponownego wiązania, dlategoxx
jest modyfikowany przezletgo2
. W przeciwieństwie do poprzedniego przykładu, tutaj nazwa lokalnax
zawsze odnosi się do wartości, do której odwołuje się nazwaxx
, i zmienia tę wartość w miejscu , dlatego ta wartośćxx
się zmieniła.W trzecim przykładzie ponownie
letgo3
wiążex
się z nowymnp.array
. To powoduje, że nazwax
lokalnaletgo3
i poprzednio odwołująca się do wartościxx
, teraz odwołuje się do innej wartości, newnp.array
. Wartość,xx
do której się odnosi, nie uległa zmianie.źródło
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.
źródło
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:
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.
źródło
Oto dokument do upuszczenia:
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.
źródło
df
wewnątrz funkcji, czy nie oznacza to, że wskazana wartość została zmieniona na nowy obiekt?musisz ustawić „a” jako globalne na początku funkcji, w przeciwnym razie jest to zmienna lokalna i nie zmienia „a” w głównym kodzie.
źródło