Фрейм данных python pandas, это передача по значению или по ссылке

88

Если я передаю фрейм данных функции и изменю его внутри функции, будет ли это передача по значению или по ссылке?

Я запускаю следующий код

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()не меняется. Почему это так?

нет
источник

Ответы:

95

Короткий ответ: Python всегда передает по значению, но каждая переменная Python на самом деле является указателем на какой-либо объект, поэтому иногда это выглядит как передача по ссылке.

В Python каждый объект является изменяемым или неизменяемым. например, списки, словари, модули и фреймы данных Pandas являются изменяемыми, а целые числа, строки и кортежи неизменяемы. Изменяемые объекты могут быть изменены внутри (например, добавить элемент в список), но неизменяемые объекты - нет.

Как я сказал в начале, вы можете думать о каждой переменной 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были изменены. (Обычно я использую глобальные переменные для общих параметров, используемых многими функциями в скрипте, но я не позволяю им изменять эти глобальные переменные.)

Маттиас Фрипп
источник
8

Чтобы добавить к ответу @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идет речь, не изменилось.

урсан
источник
7

Вопрос не в сравнении PBV и PBR. Эти имена вызывают путаницу только в таком языке, как Python; они были изобретены для языков, которые работают как C или как Fortran (как квинтэссенция языков PBV и PBR). Это правда, но не поучительно, что Python всегда передает значение. Вопрос здесь в том, мутирует ли само значение или вы получаете новое значение. Панды обычно ошибаются на стороне последнего.

http://nedbatchelder.com/text/names.html очень хорошо объясняет, что такое система имен Python.

Майк Грэм
источник
1
Семантика передачи и назначения в Python точно такая же, как и в Java, и то же самое, что вы говорите, можно в равной степени применить к Java. Тем не менее, в StackOverflow и в других местах в Интернете люди, по-видимому, находят «поучительным» внушить вам, что Java всегда обходится стороной, когда возникает эта проблема.
newacct
3

Python не передается ни по значению, ни по ссылке. Передается по назначению.

Вспомогательная ссылка, FAQ по 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]

Если вы поклонник C, вы можете думать об этом как о передаче указателя по значению, а не как указатель на указатель на значение, а просто как указатель на значение.

HTH.

Дстромберг
источник
0

Вот документ для падения:

Вернуть новый объект с удаленными метками на запрошенной оси.

Итак, создается новый фрейм данных. Оригинал не изменился.

Но что касается всех объектов в Python, фрейм данных передается в функцию по ссылке.

Израиль Унтерман
источник
но я назначил его dfвнутри функции, разве это не означает, что указанное значение было изменено на новый объект?
Присвоение локальному имени никогда не изменит, к какому объекту привязано имя в другой области.
Майк Грэм,
0

вам нужно сделать глобальную переменную «a» в начале функции, иначе это будет локальная переменная, которая не изменяет «a» в основном коде.

зосан
источник