【问题标题】:python pandas dataframe, is it pass-by-value or pass-by-referencepython pandas数据框,是按值传递还是按引用传递
【发布时间】:2016-12-18 03:47:05
【问题描述】:

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

我运行以下代码

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

【问题讨论】:

标签: python pandas pass-by-reference pass-by-value


【解决方案1】:

这是 drop 的文档:

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

因此创建了一个新的数据框。原件没有改变。

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

【讨论】:

  • 但是我在函数里面赋值给df,是不是说引用的值已经改成新的对象了?
  • 分配给本地名称永远不会更改名称绑定到另一个范围内的对象。
【解决方案2】:

您需要在函数的开头将“a”设为全局变量,否则它是一个局部变量,不会更改主代码中的“a”。

【讨论】:

    【解决方案3】:

    问题不在于 PBV 与 PBR。这些名称只会在像 Python 这样的语言中引起混淆;它们是为像 C 或 Fortran 一样工作的语言(作为典型的 PBV 和 PBR 语言)而发明的。 Python 总是按值传递是真的,但并不具有启发性。这里的问题是值本身是否发生了变异,或者您是否获得了新值。 Pandas 通常会偏向后者。

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

    【讨论】:

    • Python 中传递和赋值的语义和 Java 完全一样,你说的同样的东西也可以同样适用于 Java。然而,在 StackOverflow 和 Internet 上的其他地方,人们显然发现,每当出现这个问题时,Java 总是按价值传递给你留下深刻印象是“启发性的”。
    【解决方案4】:

    要添加到@Mike Graham 的答案,他指出了一个非常好的阅读:

    在您的情况下,重要的是要记住 namesvalues 之间的区别。 adfxxx 都是名称,但它们在示例的不同点引用相同或不同的

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

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

    • 在第三个示例中,letgo3 重新绑定 x 到新的np.array。这导致名称x,本地到letgo3,以前指的是xx 的值,现在指的是另一个值,新的np.arrayxx 所指的值没有改变。

    【讨论】:

      【解决方案5】:

      简短的回答是,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 的整个内部范围分配一些东西。

      如果您绝对必须创建一个全新的对象并使其在外部可见(熊猫有时就是这种情况),您有两种选择。 '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 是如何被更改的。 (我通常将全局变量用于脚本中许多函数使用的共享参数,但我不会让它们更改这些全局变量。)

      【讨论】:

        【解决方案6】:

        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 个值,那么在被调用者和调用者中都会看到该更改。但是,如果您使用新列表重新分配列表,则此更改将丢失。但是,如果您对列表进行切片并将 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。

        【讨论】:

        • 爱堆栈交换
        猜你喜欢
        • 2013-10-24
        • 2016-11-28
        • 2017-11-14
        相关资源
        最近更新 更多