根据this教程,Python使用“Pass By Reference。”
然后他们继续给出以下示例。在这个“通过参考传递”的星球上?它看起来像是一个明确的“通过价值”案例。
思想?
def changeme( mylist ):
mylist = [1,2,3,4];
print "Values inside the function: ", mylist
return
mylist = [10,20,30];
changeme( mylist );
print "Values outside the function: ", mylist
参数mylist是函数changeme的本地。更改函数中的mylist不会影响mylist。该函数什么都不做,最后会产生以下结果:
# Values inside the function: [1, 2, 3, 4]
# Values outside the function: [10, 20, 30]
答案 0 :(得分:5)
它是值传递,其中所有值都是指向对象的指针。您可能认为这意味着您可以使用传入的指针来更改调用者的变量,使其成为引用传递,但是您不能这样做,所以它不是。< / p>
理解Python值传递如何工作的关键是知道没有&#34;未装箱的&#34; (非对象)值。整数是对象,以及&#34;包含&#34;的变量。整数实际上是指向存储在变量之外的某个整数对象的指针。浮子,布尔,同上。
变量不是真的&#34;持有&#34; Python中的值,就像它们在C中所做的那样。因此,赋值总是包含使变量名称指向不同的对象。
如果传递给函数的对象是可变的,则该函数可以更改它,但必须完全完成此操作而不更改其名称所指向的对象。例如:
some_digits_of_pi = [3, 1, 4, 1, 5, 9, 2, 7]
def extend_pi(x):
x[-1] = 6
x += [5, 3, 5, 9]
我们在这里改变函数内的x
。 (对于列表,+=
本质上是list.extend
。)由于x
永远不会更改为指向其他对象,因此会对传入的列表进行更改。名称{ {1}}指的是与函数中被修改的对象相同的对象,因此调用者将看到他们的名称列表已被更改。
如果我们在函数末尾写some_digits_of_pi
,那将创建一个新的列表对象并指向本地名称x = [2, 7, 1, 8, 2, 8, 1, 8]
。它不会改变调用者的变量指向的内容,因此该语句不会更改列表。
换句话说,您无法使来电者变量(在这种情况下为x
)指向不同的对象。如果您将some_digits_of_pi
更改为指向函数内的其他对象,则只有x
指向该对象。
数字,字符串,元组等以完全相同的方式工作。传入指向对象的指针;如果更改函数内部参数的值,则会使其指向不同的对象,这自然不会更改调用者的变量。它只是似乎不同,因为这些类型的对象是不可变的,并且没有任何方法可以在适当的位置更改它们。
另一个令人困惑的地方是看起来,如x
和int
都有list
个运算符,但它发生在{{1} +=
上的+=
与int
上的相同操作有很大不同。在列表中,list
返回相同的列表对象(已修改),而在整数上,它可能返回一个完全不同的对象(因为整数是不可变的)。
答案 1 :(得分:4)
它既不是。它是call by sharing。我也听说过“使用参考值传递”一词。
也称为“按对象调用”或“通过对象共享调用”,通过共享调用是首先由Barbara Liskov等人命名的评估策略。对于1974年的语言CLU,它被Python,Iota,Java(用于对象引用),Ruby,JavaScript,Scheme,OCaml,AppleScript等许多语言所使用。但是,“共享呼叫”一词并不常用;不同来源的术语不一致。例如,在Java社区中,他们说Java是按值调用的,而在Ruby社区中,他们说Ruby是逐个引用的,即使这两种语言表现出相同的语义。通过共享调用意味着语言中的值基于对象而不是基本类型,即所有值都是“盒装”。
通过共享调用的语义与通过引用调用的不同之处在于,函数内的函数参数的赋值对于调用者是不可见的(与引用语义不同),例如,如果传递了变量,则无法在调用者范围内模拟该变量的赋值。但是,由于函数可以访问与调用者相同的对象(不进行复制),因此调用者可以看到函数内对这些对象的突变(如果对象是可变的),这可能看起来与按值调用不同语义。函数内的可变对象的变异对调用者是可见的,因为不会复制或克隆对象 - 它是共享的。
答案 2 :(得分:3)
根据您与谁交谈,通话的命名会有所不同。但它绝对没有的一件事,就是传递参考。
技术上和according to the docs,python都是按值传递。
[...]否则,参数的值放在槽中,填充它(即使表达式为None,它填充槽)。处理完所有参数后,仍然未填充的插槽将使用函数定义中的相应默认值填充。 [...]
现在,仅仅因为这些值实际上是引用,这导致人们争论约定并定义新名称来描述从更实际的角度来看的情况。通过值引用,通过共享,通过句柄等进行调用都是人们可以使用的不同名称。
答案 3 :(得分:1)
mylist = [1,2,3,4]
changeme()
内创建的 mylist
是一个局部变量,它不是你传入的参数,即使它与参数同名。它不引用参数mylist
答案 4 :(得分:1)
我从来不知道它的名字,但据Amadan说,它是通过分享&#34;呼叫。我会做一个类比,以便更容易理解。
让我们说我有一只狗,我给他起了名字&#34; Brutus&#34;。我带Brutus去散步,和我的邻居Sam见面。 Sam不喜欢我选择的名字,并称我的狗为“Dodo&#34;”。如果他把刀放入Dodo,它当然会影响Brutus,因为它们是同一条狗。让我们说他不会和另一个邻居比利出去和他的狗一起散步。 Sam改变主意,决定Billy的狗是Dodo。如果他把刀放入Dodo,它对Brutus没有影响,因为它们是不同的狗。
你的功能与此类似。 mylist
转到您的职能部门,您的职能部门决定给他打电话mylist
,同名。然后,您的函数会确定[1,2,3,4]
为mylist
。您现在已丢弃原始文件并定义了新列表。您的重新分配不会影响原始列表。
答案 5 :(得分:0)
根据@Amadan的回答,Python使用Call by Sharing。
在函数中,您已将引用重新分配给新列表,因此它不再指向您传入的列表。
您可以使用以下代码看到这种情况:
def changeme( mylist ):
print locals() # will show mylist value in function.
mylist = [1,2,3,4];
print locals() # will show mylist value in function.
print globals()['mylist'] # will show global mylist value
print "Values inside the function: ", mylist
return
在本地列表中分配后,您将看到mylist
的值发生变化,但全局参考仍然相同。
答案 6 :(得分:0)
就我而言,Python就像Java一样,所有函数(方法)都是按值传递的。见this infamous answer。 “通过共享调用”的缺点是它使你认为不同类型的事情发生了不同(即可变类型在函数调用中的行为与不可变类型不同)。事实并非如此,每次调用函数时都会发生同样的事情。此外,我读过的教科书中没有提到这个概念,我认为按值传递和传递参考是更受欢迎的术语。
如何通过价值传递?
它的工作原理是复制在函数中修改之前传入函数的“值”。所以,举个例子:
my_int = 4
def pass_by_value(value):
value += 1
return value
print pass_by_value(my_int)
print my_int
此输出:
5
4
观察my_int
的值未发生变化。它保持在4
,即使函数增加了传入的值为5
。这是因为它被复制了。这里有几个细微之处我们将回归。忍受我。
在列表中按值传递
my_list = [1,2,3]
def pass_by_value_list(li):
li.append(4)
return li
print pass_by_value_list(my_list)
print my_list
此输出:
[1, 2, 3, 4]
[1, 2, 3, 4]
到底是什么。 my_list
的值已更改。你只是说上面那个按值传递值复制了价值!因此,my_list
不应该改变!!!
第一个例子太微妙了。我未能正确定义正在复制的“价值”。事实证明,复制的值是而不是数据,而是存储数据的位置。通常,C / C ++程序员将其称为指针或地址。让我们重温一下这些例子,但是稍微修改它们就可以了解这一点。
价值传递如何运作?版本2.0:
my_int = 4
def pass_by_value(value):
print "Address of parameter before += is: ", id(value)
value += 1
print "Address of parameter after += is: ", id(value)
return value
print "Address of parameter outside of the function is: ", id(my_int)
pass_by_value(my_int)
当我跑这个时,我的输出是:
Address of parameter outside of the function is: 40592528
Address of parameter before += is: 40592528
Address of parameter after += is: 40592504
看起来+=
运算符之前的参数地址是40592528.这也是函数外部参数的“值”!但在+=
运算符之后,“value”更改了内部函数!但是,地址的更改未在外部函数中传播,因为地址是按值传递。函数内的新地址是40592504(与40592528不同,虽然接近)。简而言之,+=
运算符创建了一个新的int
,并且不对传递给函数的地址数据进行操作。
在列表中按值传递,版本2.0:
my_list = [1,2,3]
def pass_by_value_list(li):
print "Address of parameter before .append() is: ", id(li)
li.append(4)
print "Address of parameter after .append() is: ", id(li)
return li
print "Address of parameter outside of the function is: ", id(my_list)
pass_by_value_list(my_list)
此输出:
Address of parameter outside of the function is: 110841160
Address of parameter before .append() is: 110841160
Address of parameter after .append() is: 110841160
嘿,这与整数情况不同。看起来所有的地址都是一样的!的确,这与整数情况不同。 append
函数在适当的列表上运行,并且不返回新列表。这就是您在函数外部看到my_list
的更改的原因。 append
修改传递给函数的地址列表,因此我们看到整个程序中的数据都发生了变化。然而,没有改变的是地址。
观察:
my_list = [1,2,3]
def dont_change_the_list(li):
li.append(4)
li = []
return li
print dont_change_the_list(my_list)
print my_list
输出
[]
[1, 2, 3, 4]
换句话说,函数内部列表的更改并未传播到函数外部,即使这似乎是我们之前看到的行为。这是因为语句li = []
更改了li
的地址,但我们在执行函数之前复制了地址。 参数的地址是复制的“值”,无法在函数内部进行更改。因此,这并没有改变主程序中my_list
的数据。
关键点:Python总是在其函数调用中使用按值传递语义。该值恰好是传入的对象的地址,而不是传入的对象的数据。