python pandas фрейм даних, це передача за значенням або передача за посиланням


84

Якщо я передаю фрейм даних функції та модифікую її всередині функції, це передає значення або передає посилання?

Я запускаю такий код

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

значення aне змінюється після виклику функції. Чи означає це, що це побічне значення?

Я також спробував наступне

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

Виявляється, letgo2()це змінюється xxі letgo3()ні. Чому це так?


Відповіді:


91

Коротка відповідь полягає в тому, що Python завжди передає значення, але кожна змінна Python насправді є вказівником на якийсь об'єкт, тому іноді це виглядає як передача-посилання.

У Python кожен об'єкт є або змінним, або незмінним. наприклад, списки, дикти, модулі та фрейми даних Pandas можна змінювати, а ints, рядки та кортежі не можна змінювати. Змінні об'єкти можна змінювати внутрішньо (наприклад, додати елемент до списку), але незмінні об'єкти не можуть.

Як я вже говорив на початку, ви можете думати про кожну змінну Python як про вказівник на об'єкт. Коли ви передаєте змінну функції, змінна (покажчик) у функції завжди є копією змінної (покажчика), яка була передана. Отже, якщо ви призначите щось нове для внутрішньої змінної, все, що ви робите, це змінити локальна змінна для вказівки на інший об’єкт. Це не змінює (мутує) початковий об'єкт, на який вказувала змінна, і не робить зовнішню змінну вказівкою на новий об'єкт. На даний момент зовнішня змінна все ще вказує на вихідний об'єкт, але внутрішня змінна вказує на новий об'єкт.

Якщо ви хочете змінити вихідний об'єкт (можливо лише із змінними типами даних), вам потрібно зробити щось, що змінює об'єкт, не призначаючи локальному змінному абсолютно нового значення. Ось чому letgo()і letgo3()залиште зовнішній елемент незмінним, але letgo2()змінює його.

Як зазначив @ursan, якщо letgo()замість цього використовувати щось подібне, він змінить (мутує) оригінальний об'єкт, на який dfвказує, що змінить значення, яке бачиться через глобальну aзмінну:

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

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

У деяких випадках ви можете повністю видовбати вихідну змінну та поповнити її новими даними, фактично не виконуючи прямого присвоєння, наприклад, це змінить оригінальний об'єкт, на який vвказує, що змінить дані, побачені при vподальшому використанні :

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

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

Зверніть увагу, що я не призначаю щось безпосередньо x; Я присвоюю щось усьому внутрішньому діапазону x.

Якщо вам абсолютно необхідно створити абсолютно новий об'єкт і зробити його видимим зовні (що іноді буває у панд), у вас є два варіанти. Варіант "чистого" був би просто повернути новий об'єкт, наприклад,

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

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

Іншим варіантом було б вийти за межі вашої функції та безпосередньо змінити глобальну змінну. Це змінюється aна вказівку на новий об’єкт, і будь-яка функція, яка посилається aзгодом, побачить цей новий об’єкт:

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

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

Безпосередньо змінювати глобальні змінні, як правило, погана ідея, тому що кожному, хто читає ваш код, буде важко зрозуміти, як aце змінилося. (Я зазвичай використовую глобальні змінні для спільних параметрів, що використовуються багатьма функціями сценарію, але я не дозволяю їм змінювати ці глобальні змінні.)


7

Питання не в PBV проти PBR. Ці імена лише викликають плутанину в такій мові, як Python; вони були винайдені для мов, які працюють як C або як Fortran (як типові мови PBV та PBR). Це правда, але не просвітницьке, що Python завжди передає значення. Питання тут полягає в тому, чи змінено саме значення, чи ви отримали нове значення. Панда зазвичай помиляється на стороні останнього.

http://nedbatchelder.com/text/names.html дуже добре пояснює, що таке система імен Python.


1
Семантика передавання та присвоєння в Python точно така ж, як у Java, і ті самі речі, які ви говорите, можуть бути однаково застосовані до Java. Проте в StackOverflow та інших місцях в Інтернеті люди, мабуть, вважають "просвітливим" вражати вас, що Java завжди передається цінністю, коли виникає ця проблема.
newacct

7

Щоб додати до відповіді @Mike Graham, який вказав на дуже добре прочитане:

У вашому випадку важливо пам’ятати різницю між іменами та значеннями . a, df, xx, x, Все імена , але вони відносяться до тих же або різних значень в різних точках ваших прикладів:

  • У першому прикладі letgo повторно прив'язується df до іншого значення, оскільки df.dropповертає нове, DataFrameякщо ви не встановили аргумент inplace = True( див. Документ ). Це означає, що ім'я df(локальне для letgoфункції), яке посилалося на значення a, тепер посилається на нове значення, тут df.dropповернене значення. Значення, на aяке посилається, все ще існує і не змінилося.

  • У другому прикладі letgo2 мутує x , не перев’язуючи його, саме тому xxмодифікується letgo2. На відміну від попереднього прикладу, тут місцеве ім'я xзавжди посилається на значення, на яке xxпосилається ім'я , і змінює це значення на місці , саме тому значення, на xxяке посилається, змінилося.

  • У третьому прикладі letgo3 повторно пов’язується x з новим np.array. Це призводить до того, що ім’я x, місцеве letgo3та яке раніше посилалося на значення xx, тепер посилається на інше значення, нове np.array. Значення, на xxяке посилається, не змінилося.


3

Python - це не передача за значенням, ані передача за посиланням. Це проходить за призначенням.

Довідкове посилання, поширені запитання щодо Python: https://docs.python.org/3/faq/programming.html#how-do-i-write-a-function-with-output-parameters-call-by-reference

IOW:

  1. Якщо ви передаєте незмінне значення, зміни до нього не змінять його значення у абоненті - оскільки ви перев'язуєте ім'я до нового об'єкта.
  2. Якщо ви передаєте змінне значення, зміни, внесені у викликану функцію, також змінюють значення у абоненті, якщо ви не перев'язуєте це ім'я до нового об'єкта. Якщо ви перепризначаєте змінну, створюючи новий об'єкт, ця зміна та подальші зміни в назві не відображаються у абонента.

Отже, якщо ви передаєте список і змінюєте його 0-те значення, ця зміна помітна як у викликаного, так і у абонента. Але якщо ви перепризначите список новим списком, ця зміна буде втрачена. Але якщо ви відрізаєте список і замінити , що з новим списком, що зміна проявляється в обох викликається і викликає.

Наприклад:

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]

Якщо ви фанат С, ви можете думати про це як про передачу покажчика за значенням - не вказівника на вказівник на значення, а просто вказівника на значення.

HTH.


0

Ось документ для падіння:

Повернути новий об'єкт із видаленими мітками в запитуваній осі.

Тож створюється новий фрейм даних. Оригінал не змінився.

Але як і для всіх об’єктів у python, кадр даних передається функції за допомогою посилання.


але я призначив його dfвсередині функції, чи не означає це, що вказане значення було змінено на новий об'єкт?
nos

Присвоєння локальному імені ніколи не змінить того, до якого об’єкта пов’язане ім’я в іншій області.
Mike Graham

0

вам потрібно зробити "a" глобальним на початку функції, інакше це локальна змінна і не змінює "a" в основному коді.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.