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

时间:2016-08-11 11:59:13

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

如果我将一个数据帧传递给一个函数并在函数内修改它,它是按值传递还是按引用传递?

我运行以下代码

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()没有更改validation API. I'm trying to use the ClassValidator class. But the import required for it, org.hibernate.validator.ClassValidator<User.class> is not getting resolved. I also have the compatible dependencies <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.2.4.Final</version> 。为什么会这样?

6 个答案:

答案 0 :(得分:57)

简短的回答是,Python总是按值传递,但每个Python变量实际上都是指向某个对象的指针,所以有时它看起来像是通过引用传递。

在Python中,每个对象都是可变的或不可变的。例如,列表,dicts,模块和Pandas数据帧是可变的,并且int,字符串和元组是不可变的。可以在内部更改可变对象(例如,将元素添加到列表中),但不可变对象不能。

正如我在开始时所说,您可以将每个Python变量视为指向对象的指针。将变量传递给函数时,函数中的变量(指针)始终是传入的变量(指针)的副本。因此,如果为内部变量分配新内容,则所做的只是更改局部变量指向不同的对象。这不会改变(变异)变量指向的原始对象,也不会使外部变量指向新对象。此时,外部变量仍指向原始对象,但内部变量指向新对象。

如果要更改原始对象(仅适用于可变数据类型),则必须执行一些操作来更改对象,而不为局部变量分配全新值。这就是letgo()letgo3()保持外部项不变的原因,但letgo2()会改变它。

正如@ursan指出的那样,如果letgo()使用了类似的东西,那么它会改变(变异)df指向的原始对象,这会改变通过全局{{{1}看到的值。 {1}}变量:

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的整个内部范围分配内容。

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

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

答案 1 :(得分:7)

问题不是PBV与PBR。这些名称只会导致像Python这样的语言混淆;它们是为像C语言一样的语言或像Fortran(作为典型的PBV和PBR语言)发明的。 Python总是按值传递,但这并不具有启发性。这里的问题是价值本身是否发生了变化,或者你是否获得了新的价值。熊猫通常在后者的一边犯错。

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

答案 2 :(得分:4)

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

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

  • 在第一个示例中,letgo 重新绑定 df为另一个值,因为df.drop会返回新的DataFrame,除非您设置参数inplace = Truesee doc)。这意味着名称dfletgo函数的本地名称),指的是a的值,现在指的是新值,此处为df.drop回报价值。 a所指的值仍然存在且未更改。

  • 在第二个示例中,letgo2 变异 x,而不重新绑定它,这就是xx修改letgo2的原因。与前面的示例不同,此处本地名称x始终引用名称xx所引用的值,并将值更改为,这就是值{ {1}}指的是已更改。

  • 在第三个示例中,xx letgo3重新绑定到新的x。这导致名称np.arrayx的本地名称以及之前引用letgo3的值,现在引用另一个值,即新的xx。值np.array表示尚未更改。

答案 3 :(得分:1)

Python既不是通过值传递也不是通过引用传递。它是通过作业传递的。

支持参考,Python FAQ: https://docs.python.org/3/faq/programming.html#how-do-i-write-a-function-with-output-parameters-call-by-reference

IOW:

  1. 如果传递不可变值,则对其进行更改不会更改它 调用者中的值 - 因为您将名称重新绑定到新名称 对象。
  2. 如果传递一个可变值,则在被调用函数中进行更改, 只要不重新绑定,也可以更改调用者的值 该名称为新对象。如果重新分配变量, 创建一个新对象,该更改和后续更改 在呼叫者中看不到姓名。
  3. 因此,如果您传递一个列表并更改其第0个值,则在被调用者和调用者中都会看到该更改。但是,如果使用新列表重新分配列表,则此更改将丢失。但是,如果您对列表进行切片并将 替换为新列表,则可以在被叫和调用者中看到该更改。

    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。

答案 4 :(得分:0)

这是drop的文档:

  

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

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

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

答案 5 :(得分:0)

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