pandas python传送 Dataframe ,是按值传递还是按引用传递

0ejtzxu1  于 2022-12-16  发布在  Python
关注(0)|答案(7)|浏览(300)

如果我把一个 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()没有,为什么会这样呢?

nkhmeac6

nkhmeac61#

简短的回答是,Python总是通过值传递,但是每个Python变量实际上都是指向某个对象的指针,所以有时候看起来像是通过引用传递。
在Python中,每个对象要么是可变的,要么是不可变的。例如,列表、字典、模块和Pandas Dataframe 是可变的,而int、字符串和元组是不可变的。可变对象可以在内部改变(例如,向列表添加元素),但不可变对象不能。
正如我在开头所说的,你可以把每个Python变量看作是一个指向对象的指针,当你把一个变量传递给一个函数时,这个变量(指针)始终是变量的副本(指针)。所以如果你给内部变量赋值,你所做的只是改变局部变量指向一个不同的对象。(mutate)变量所指向的原对象,也不使外部变量指向新对象,此时外部变量仍指向原对象,但内部变量指向一个新对象。
如果你想改变原始对象(只适用于可变数据类型),你必须改变对象,而不给局部变量赋值,这就是为什么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”选项只返回新对象,例如:

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

smdncfj3

smdncfj32#

为了补充“迈克·格雷厄姆的回答,他指出了一个非常好的阅读:
在您的示例中,需要记住的是 namesvalues 之间的区别。adfxxx都是 names,但它们在示例的不同位置引用相同或不同的 values

  • 在第一个示例中,letgodf重新绑定到另一个值,因为df.drop将返回新的DataFrame,除非您设置参数inplace = Truesee doc)。这意味着名称dfletgo函数的本地),其引用a的值,现在引用新的值,这里是df.drop的返回值。a所引用的值仍然存在并且没有改变。
  • 在第二个示例中,letgo2x进行了变异,但没有对其进行重新绑定,这就是xxletgo2修改的原因。与上一个示例不同,这里的本地名称x始终引用名称xx所引用的值,并将该值更改为 in place。这就是xx所指的值已经改变的原因。
  • 在第三个示例中,letgo3x重新绑定到一个新的np.array。这将导致名称xletgo3的本地名称,以前引用xx的值)现在引用另一个值,即新的np.arrayxx所引用的值没有更改。
hjqgdpho

hjqgdpho3#

问题不在于PBV和PBR,这些名称只会在Python这样的语言中引起混淆;它们是为类似C或Fortran的语言(作为典型的PBV和PBR语言)而发明的。Python总是通过值传递,这是事实,但没有启发性。这里的问题是值本身是变异的还是你得到了一个新的值。Pandas通常在后一种情况下出错。
http://nedbatchelder.com/text/names.html很好地解释了Python的名称系统是什么。

mznpcxlj

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,那么这个改变在被调用者和调用者中都能看到。
例如:

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

fslejnso

fslejnso5#

下面是Drop的文档:
返回已删除所请求轴中标签的新对象。
因此创建了一个新的 Dataframe ,原始 Dataframe 没有改变。
但是对于Python中的所有对象来说, Dataframe 都是通过引用传递给函数的。

cbjzeqam

cbjzeqam6#

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

y1aodyip

y1aodyip7#

简短回答:

  • 按值:df2 = df.copy()
  • 参考文献:df2 = df

相关问题