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()
确实改变了 xx
而 letgo3()
没有。为什么会这样?
这是 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 的回答中,他指出了一个很好的读物:
对于您的情况,重要的是要记住 names 和 values 之间的区别。 a
、df
、xx
、x
,都是名字,但指的是相同或不同的values 在你的例子的不同点:
在第一个例子中,letgo
重新绑定df
到另一个值,因为df.drop
return 是一个新的 DataFrame
,除非您设置参数 inplace = True
(see doc)。这意味着名称 df
(局部于 letgo
函数)指的是 a
的值,现在指的是一个新值,这里是 df.drop
return 值。 a
所指的值仍然存在且未更改。
在第二个例子中,letgo2
mutatesx
,没有重新绑定它,这就是为什么xx
是由 letgo2
修改。与前面的示例不同,这里的本地名称 x
始终引用名称 xx
所引用的值,并在 位置更改该值 ,这就是为什么xx
所指的值已更改。
在第三个例子中,letgo3
重新绑定x
到一个新的np.array
。这导致名称 x
,本地于 letgo3
并且之前引用 xx
的值,现在引用另一个值,新的 np.array
。 xx
所指的值没有改变。
简短的回答是,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:
- 如果你传递一个不可变的值,对它的改变不会改变它的
调用者中的值 - 因为您将名称重新绑定到一个新的
对象。
- 如果您传递一个可变值,在被调用函数中所做的更改,
也改变调用者的价值,只要你不重新绑定
那个名字给一个新的对象。如果重新分配变量,
创建一个新对象,该对象的更改和后续更改
在来电者中看不到名字。
因此,如果您传递一个列表并更改其第 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.
如果我将数据帧传递给函数并在函数内部修改它,它是按值传递还是按引用传递?
我运行以下代码
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()
没有。为什么会这样?
这是 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 的回答中,他指出了一个很好的读物:
对于您的情况,重要的是要记住 names 和 values 之间的区别。 a
、df
、xx
、x
,都是名字,但指的是相同或不同的values 在你的例子的不同点:
在第一个例子中,
letgo
重新绑定df
到另一个值,因为df.drop
return 是一个新的DataFrame
,除非您设置参数inplace = True
(see doc)。这意味着名称df
(局部于letgo
函数)指的是a
的值,现在指的是一个新值,这里是df.drop
return 值。a
所指的值仍然存在且未更改。在第二个例子中,
letgo2
mutatesx
,没有重新绑定它,这就是为什么xx
是由letgo2
修改。与前面的示例不同,这里的本地名称x
始终引用名称xx
所引用的值,并在 位置更改该值 ,这就是为什么xx
所指的值已更改。在第三个例子中,
letgo3
重新绑定x
到一个新的np.array
。这导致名称x
,本地于letgo3
并且之前引用xx
的值,现在引用另一个值,新的np.array
。xx
所指的值没有改变。
简短的回答是,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:
- 如果你传递一个不可变的值,对它的改变不会改变它的 调用者中的值 - 因为您将名称重新绑定到一个新的 对象。
- 如果您传递一个可变值,在被调用函数中所做的更改, 也改变调用者的价值,只要你不重新绑定 那个名字给一个新的对象。如果重新分配变量, 创建一个新对象,该对象的更改和后续更改 在来电者中看不到名字。
因此,如果您传递一个列表并更改其第 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.