如果我把一个 Dataframe 传递给一个函数,并在函数内部修改它,它是通过值传递还是通过引用传递?
我运行以下代码
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()
没有,为什么会这样呢?
7条答案
按热度按时间nkhmeac61#
简短的回答是,Python总是通过值传递,但是每个Python变量实际上都是指向某个对象的指针,所以有时候看起来像是通过引用传递。
在Python中,每个对象要么是可变的,要么是不可变的。例如,列表、字典、模块和Pandas Dataframe 是可变的,而int、字符串和元组是不可变的。可变对象可以在内部改变(例如,向列表添加元素),但不可变对象不能。
正如我在开头所说的,你可以把每个Python变量看作是一个指向对象的指针,当你把一个变量传递给一个函数时,这个变量(指针)始终是变量的副本(指针)。所以如果你给内部变量赋值,你所做的只是改变局部变量指向一个不同的对象。(mutate)变量所指向的原对象,也不使外部变量指向新对象,此时外部变量仍指向原对象,但内部变量指向一个新对象。
如果你想改变原始对象(只适用于可变数据类型),你必须改变对象,而不给局部变量赋值,这就是为什么
letgo()
和letgo3()
保持外部项不变,但是letgo2()
改变了它。正如@ursan所指出的,如果
letgo()
使用类似于下面的内容,那么它将改变(变异)df
所指向的原始对象,这将改变通过全局变量a
看到的值:在某些情况下,你可以完全清空原来的变量,然后用新的数据填充它,而不需要直接赋值,例如,这将改变
v
指向的原始对象,这将改变你以后使用v
时看到的数据:注意,我没有直接给
x
赋值;我给x
的整个内部范围赋值。如果你一定要创建一个全新的对象并使其在外部可见(有时候Pandas就是这种情况),你有两个选择:“clean”选项只返回新对象,例如:
另一种方法是直接修改函数的全局变量,这会将
a
修改为指向一个新对象,之后任何引用a
的函数都会看到这个新对象:直接修改全局变量通常不是一个好主意,因为任何阅读您的代码的人都很难弄清楚
a
是如何被修改的(我通常使用全局变量作为脚本中许多函数使用的共享参数,但我不会让它们修改这些全局变量)。smdncfj32#
为了补充“迈克·格雷厄姆的回答,他指出了一个非常好的阅读:
在您的示例中,需要记住的是 names 和 values 之间的区别。
a
、df
、xx
、x
都是 names,但它们在示例的不同位置引用相同或不同的 values:letgo
将df
重新绑定到另一个值,因为df.drop
将返回新的DataFrame
,除非您设置参数inplace = True
(see doc)。这意味着名称df
(letgo
函数的本地),其引用a
的值,现在引用新的值,这里是df.drop
的返回值。a
所引用的值仍然存在并且没有改变。letgo2
对x
进行了变异,但没有对其进行重新绑定,这就是xx
被letgo2
修改的原因。与上一个示例不同,这里的本地名称x
始终引用名称xx
所引用的值,并将该值更改为 in place。这就是xx
所指的值已经改变的原因。letgo3
将x
重新绑定到一个新的np.array
。这将导致名称x
(letgo3
的本地名称,以前引用xx
的值)现在引用另一个值,即新的np.array
。xx
所引用的值没有更改。hjqgdpho3#
问题不在于PBV和PBR,这些名称只会在Python这样的语言中引起混淆;它们是为类似C或Fortran的语言(作为典型的PBV和PBR语言)而发明的。Python总是通过值传递,这是事实,但没有启发性。这里的问题是值本身是变异的还是你得到了一个新的值。Pandas通常在后一种情况下出错。
http://nedbatchelder.com/text/names.html很好地解释了Python的名称系统是什么。
mznpcxlj4#
Python既不是按值传递,也不是按引用传递,而是按赋值传递。
支持参考,Python常见问题解答:https://docs.python.org/3/faq/programming.html#how-do-i-write-a-function-with-output-parameters-call-by-reference
欠条:
1.如果您传递一个不可变的值,对它的更改不会更改它在调用方中的值-因为您是将名称重新绑定到一个新对象。
1.如果传递一个可变值,则只要不将该名称重新绑定到新对象,在被调用函数中所做的更改也会更改调用方中的值。如果重新分配变量,创建一个新对象,则在调用方中看不到对名称所做的更改和后续更改。
因此,如果你传递一个列表,并改变它的第0个值,那么这个改变在被调用者和调用者中都能看到;但是如果你用一个新的列表重新分配这个列表,那么这个改变就丢失了;但是如果你对这个列表进行切片,并用一个新的列表替换 that,那么这个改变在被调用者和调用者中都能看到。
例如:
如果你是一个C爱好者,你可以把它看作是通过值传递一个指针--不是一个指向一个值的指针的指针,只是一个指向一个值的指针。
嗯。
fslejnso5#
下面是Drop的文档:
返回已删除所请求轴中标签的新对象。
因此创建了一个新的 Dataframe ,原始 Dataframe 没有改变。
但是对于Python中的所有对象来说, Dataframe 都是通过引用传递给函数的。
cbjzeqam6#
你需要在函数的开头使'a'成为全局变量,否则它是一个局部变量,不会改变主代码中的'a'。
y1aodyip7#
简短回答:
df2 = df.copy()
df2 = df