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


84

如果我将数据帧传递给函数并在函数内部对其进行修改,那么它是按值传递还是按引用传递?

我运行以下代码

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


Answers:


90

简短的答案是,Python始终会传递值,但每个Python变量实际上都是指向某个对象的指针,因此有时看起来像是传递引用。

在Python中,每个对象都是可变的或不可更改的。例如,列表,字典,模块和熊猫数据帧是可变的,而整数,字符串和元组是不可变的。可变对象可以在内部进行更改(例如,将元素添加到列表中),但非可变对象则不能。

正如我在开始时所说的,您可以将每个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

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

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


7

问题不是PBV与PBR。这些名称只会引起像Python这样的语言的混乱;它们是为像C或Fortran这样的语言(作为典型的PBV和PBR语言)而发明的。的确,Python总是按值传递,但没有启发性。这里的问题是值本身是否已突变或您是否获得了新值。熊猫通常会偏向后者。

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


1
Python中传递和分配的语义与Java中的语义完全相同,您说的相同内容也可以同样地应用于Java。然而,在StackOverflow和Internet上的其他地方,人们似乎发现,“启发”使您印象深刻的是,只要出现此问题,Java总是通过价值传递。
newacct

7

为了补充@Mike Graham的答案,他指出了一个很好的读物:

在您的情况下,要记住的重要一点是名称之间的区别。adfxxx,都是名字,但它们指的是相同或不同的值,在你的例子不同点:

  • 在第一个示例中,letgo 重新绑定 df到另一个值,因为除非设置了参数,否则df.drop返回一个新值(请参阅doc)。这意味着名称(对于函数而言是本地的),它引用的值,现在引用的是新值,此处为返回值。该值指的是仍然存在,并且没有更改。DataFrameinplace = Truedfletgoadf.dropa

  • 在第二个示例中,将letgo2 mutate x而不重新绑定它,这就是为什么xx要通过对其进行修改的原因letgo2。与前面的示例不同,此处的本地名称x始终引用名称xx所引用的值,并在适当位置更改该值,这就是该值xx引用的原因已更改的原因。

  • 在第三个示例中,letgo3 重新绑定 x到新的np.array。这会导致名称x(本地letgo3且以前引用过该值)xx现在引用另一个值new np.arrayxx所指的值未更改。


3

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迷,则可以认为这是按值传递指针-不是指向值的指针,而是指向值的指针。

HTH。


0

这是放置文档:

返回删除了请求轴上的标签的新对象。

这样就创建了一个新的数据框。原来没有改变。

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


但是我将其分配给df了函数内部,这是否意味着引用值已更改为新对象?

分配给本地名称将永远不会更改名称在另一个范围内绑定到的对象。
Mike Graham

0

您需要在函数的开始处将'a'设置为全局,否则它是局部变量,并且不会更改主代码中的'a'。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.