Python 熊猫数据帧,是按值传递还是按引用传递

如果我将一个数据框传递给一个函数并在函数内部修改它,它是按值传递还是按引用传递?

我运行以下代码

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()没有。为什么会这样呢?

116056 次浏览

下面是下降的文件:

返回删除所请求轴中标签的新对象。

所以创建了一个新的数据框架,原始数据框架没有改变。

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

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

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

Http://nedbatchelder.com/text/names.html 很好地解释了 Python 的名字系统是什么。

加上@Mike Graham 的回答,他指出了一篇非常好的文章:

在你的例子中,重要的是要记住 名字价值观之间的区别。adfxxx都是 名字,但是它们在例子的不同部分指的是相同或不同的 价值观:

  • 在第一个示例中,letgo df0 df转换为另一个值,因为除非设置参数 inplace = True(df1) ,否则 df.drop返回一个新的 DataFrame。这意味着名称 df(letgo函数的本地名称)指的是 a的值,现在指的是一个新值,这里是 df.drop返回值。a所指的值仍然存在并且没有改变。

  • 在第二个示例中,letgo2 变异 x,没有重新绑定它,这就是 xxletgo2修改的原因。与前面的示例不同,这里的本地名称 x始终引用名称 xx所引用的值,并更改该值 就位,这就是 xx所引用的值发生更改的原因。

  • 在第三个例子中,letgo3 复活 x变成了一个新的 np.array。这导致名称 x(letgo3的本地名称,以前指的是 xx的值)现在指的是另一个值,即新的 np.arrayxx所指的值没有改变。

简短的回答是,Python 总是按值传递,但是每个 Python 变量实际上都是指向某个对象的指针,所以有时它看起来像是按引用传递。

在 Python 中,每个对象要么是可变的,要么是不可变的。例如,列表、字典、模块和熊猫数据框架是可变的,而 int、字符串和元组是不可变的。可变对象可以在内部进行更改(例如,向列表中添加元素) ,但非可变对象不能。

正如我在开始时所说,您可以将每个 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的整个内部范围赋值。

如果您绝对必须创建一个全新的对象并使其在外部可见(熊猫有时就是这种情况) ,那么您有两种选择。“ clean”选项只是返回新对象,例如,

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个值,则在被调用方和调用方中都可以看到该更改。但是,如果使用新列表重新分配列表,则此更改将丢失。但是,如果将该列表切片,并用一个新列表替换 那个,则在被调用方和调用方都可以看到这种更改。

例如:

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 爱好者,你可以把它看作是通过值传递一个指针——不是一个指向指向值的指针,只是一个指向值的指针。

高温。