nos*_*nos 58 python pass-by-reference pass-by-value pandas
如果我将数据帧传递给函数并在函数内修改它,它是按值传递还是按引用传递?
我运行以下代码
a = pd.DataFrame({'a':[1,2], 'b':[3,4]})
def letgo(df):
df = df.drop('b',axis=1)
letgo(a)
Run Code Online (Sandbox Code Playgroud)
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]])
Run Code Online (Sandbox Code Playgroud)
事实证明letgo2()确实发生了变化xx而letgo3()没有变化.为什么会这样?
Mat*_*ipp 65
简短的回答是,Python总是按值传递,但每个Python变量实际上都是指向某个对象的指针,因此有时它看起来像是传递引用.
在Python中,每个对象都是可变的或不可变的.例如,列表,dicts,模块和Pandas数据帧是可变的,并且int,字符串和元组是不可变的.可以在内部更改可变对象(例如,将元素添加到列表中),但不可变对象不能.
正如我在开始时所说,您可以将每个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
Run Code Online (Sandbox Code Playgroud)
在某些情况下,您可以完全挖空原始变量并使用新数据重新填充,而无需实际执行直接分配,例如,这将更改v指向的原始对象,这将更改v以后使用时看到的数据:
def letgo3(x):
x[:] = np.array([[3,3],[3,3]])
v = np.empty((2, 2))
letgo3(v) # will alter v
Run Code Online (Sandbox Code Playgroud)
请注意,我没有直接分配内容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)
Run Code Online (Sandbox Code Playgroud)
另一个选择是到达函数外部并直接更改全局变量.这会更改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!
Run Code Online (Sandbox Code Playgroud)
直接改变全局变量通常是一个坏主意,因为任何读取代码的人都很难弄清楚如何a改变.(我通常将全局变量用于脚本中许多函数使用的共享参数,但我不允许它们改变那些全局变量.)
问题不是PBV与PBR.这些名称只会导致像Python这样的语言混淆; 它们是为像C语言一样的语言或像Fortran(作为典型的PBV和PBR语言)发明的.Python总是按值传递,但这并不具有启发性.这里的问题是价值本身是否发生了变化,或者你是否获得了新的价值.熊猫通常在后者的一边犯错.
http://nedbatchelder.com/text/names.html很好地解释了Python的名称系统.
Python 既不是按值传递,也不是按引用传递。它是通过赋值传递的。
支持参考,Python FAQ:https : //docs.python.org/3/faq/programming.html#how-do-i-write-a-function-with-output-parameters-call-by-reference
爱荷华州:
因此,如果您传递一个列表,并更改其第 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]
Run Code Online (Sandbox Code Playgroud)
如果您是 C 迷,您可以将其视为按值传递指针 - 不是指向值的指针的指针,而是指向值的指针。
哈。
要补充@Mike Graham的答案,他指出了一个很好的读物:
在您的情况下,要记住的重要一点是名称和值之间的区别。a,df,xx,x,都是名字,但它们指的是相同或不同的值,在你的例子不同点:
在第一个示例中,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.array。xx所指的值未更改。