python pandas dataframe,是传值还是传引用

python pandas dataframe, is it pass-by-value or 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() 确实改变了 xxletgo3() 没有。为什么会这样?

这是 drop 的文档:

Return new object with labels in requested axis removed.

因此创建了一个新的数据框。原文没改。

但对于 python 中的所有对象,数据框通过引用传递给函数。

你需要在函数的开头使'a'成为全局变量,否则它是一个局部变量并且不会改变主代码中的'a'。

问题不是 PBV 与 PBR。这些名称只会在 Python 这样的语言中引起混淆;它们是为像 C 或 Fortran 一样工作的语言(作为典型的 PBV 和 PBR 语言)而发明的。 Python 总是按值传递,这是事实,但并不具有启发性。这里的问题是值本身是否发生了变化,或者您是否获得了新值。 Pandas 通常会偏向后者。

http://nedbatchelder.com/text/names.html 很好地解释了 Python 的名称系统。

添加到@Mike Graham 的回答中,他指出了一个很好的读物:

对于您的情况,重要的是要记住 namesvalues 之间的区别。 adfxxx,都是名字,但指的是相同或不同的values 在你的例子的不同点:

  • 在第一个例子中,letgo重新绑定df到另一个值,因为df.dropreturn 是一个新的 DataFrame,除非您设置参数 inplace = True (see doc)。这意味着名称 df(局部于 letgo 函数)指的是 a 的值,现在指的是一个新值,这里是 df.drop return 值。 a 所指的值仍然存在且未更改。

  • 在第二个例子中,letgo2mutatesx,没有重新绑定它,这就是为什么xx是由 letgo2 修改。与前面的示例不同,这里的本地名称 x 始终引用名称 xx 所引用的值,并在 位置更改该值 ,这就是为什么xx 所指的值已更改。

  • 在第三个例子中,letgo3重新绑定x到一个新的np.array。这导致名称 x,本地于 letgo3 并且之前引用 xx 的值,现在引用另一个值,新的 np.arrayxx 所指的值没有改变。

简短的回答是,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.

的整个内部范围分配一些东西

如果您绝对必须创建一个全新的对象并使其在外部可见(pandas 有时就是这种情况),您有两个选择。 'clean' 选项只是 return 新对象,例如

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 是如何更改的。 (我通常将全局变量用于脚本中许多函数使用的共享参数,但我不会让它们更改这些全局变量。)

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 个值,则该更改会在被调用方和调用方中看到。但是,如果您使用新列表重新分配列表,则此更改将丢失。但是,如果您对列表进行切片并将 that 替换为新列表,则在被调用方和调用方中都会看到该更改。

EG:

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.