Python pandas dataframe, это pass-by-value или pass-by-reference

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

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

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

6 ответов


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

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

Как Я сказанное в начале, вы можете думать о каждой переменной Python как о указателе на объект. Когда вы передаете переменную функции, переменная (указатель) внутри функции всегда является копией переменной (указателя), которая была передана. Поэтому, если вы назначаете что-то новое внутренней переменной, все, что вы делаете, это меняете локальную переменную, чтобы указать на другой объект. Это не изменяет (мутирует) исходный объект, на который указывала переменная, и не делает внешнюю переменную точкой новый объект. На этом этапе внешняя переменная все еще указывает на исходный объект, но внутренняя переменная указывает на новый объект.

если вы хотите изменить исходный объект (возможно только с изменяемыми типами данных), вы должны сделать что-то, что изменяет объект без присвоение совершенно нового значения локальной переменной. Вот почему letgo() и letgo3() оставьте внешний элемент без изменений, но letgo2() изменяет его.

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


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

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


чтобы добавить к ответу @Mike Graham, который указал на очень хорошее чтение:

в вашем случае важно помнить, что разница между имена и значения. a, df, xx, x, все имена, но они относятся к тому же или другому значения в разных точках вашего примера:

  • в первом примере, letgo rebinds df к другому значению, потому что df.drop возвращает новый DataFrame если не задан аргумент inplace = True (см. doc). Это означает, что имя df (местных к letgo функция), которая имела в виду значение a, теперь относится к новому значению, здесь df.drop возвращаемое значение. Значение a ссылается на все еще существует и не изменился.

  • во втором примере, letgo2 мутирует x, без перематывая его, вот почему xx изменен на letgo2. В отличие от предыдущего примера, здесь локальное имя x всегда ссылается на значение имя xx ссылается на и изменяет это значение на месте, поэтому значение xx имеет в виду изменилось.

  • в третьем примере, letgo3 rebinds x новая np.array. Это вызывает имя x, участкового letgo3 и ранее ссылаясь на значение xx, теперь обратитесь к другому значению, новому np.array. Значение xx имеет в виду не изменилось.


Python не является ни pass by value, ни pass by reference. Это пропуск по заданию.

поддержка ссылки, Python FAQ: 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.


вот документ для drop:

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

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

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


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