这怎么能叫引用传递呢?

How can this be called Pass By Reference?

根据 this 教程,Python 使用“按引用传递”。

他们接着举了下面的例子。这个“参考传递”是在哪个星球上?对我来说,这看起来像是一个明确的“按值传递”案例。

想法?

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

The parameter mylist is local to the function changeme. Changing mylist within the function does not affect mylist. The function accomplishes nothing and finally this would produce the following result:

# Values inside the function:  [1, 2, 3, 4]
# Values outside the function:  [10, 20, 30]

更新、结论:

  1. 根据下面 viraptor 的回答,“根据文档,python 是按值传递”。所以教程明显是错误的。

  2. Python 不是“引用调用”,而是“共享调用”。这意味着可以将引用传递给本地子例程,由子例程更新,并且更新保留在子例程的本地,不会自动影响引用的全局值。

根据@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的值在本地列表中赋值后发生变化,但全局引用仍然相同。

mylist = [1,2,3,4]是在changeme()内部创建的mylist是一个局部变量,它不是你传入的参数,即使它与参数同名。它不引用参数 mylist

两者都不是。是call by sharing。我也听说过“按参考值传递”这个词。

Also known as "call by object" or "call by object-sharing," call by sharing is an evaluation strategy first named by Barbara Liskov et al. for the language CLU in 1974. It is used by languages such as Python, Iota, Java (for object references), Ruby, JavaScript, Scheme, OCaml, AppleScript, and many others. However, the term "call by sharing" is not in common use; the terminology is inconsistent across different sources. For example, in the Java community, they say that Java is call-by-value, whereas in the Ruby community, they say that Ruby is call-by-reference, even though the two languages exhibit the same semantics. Call by sharing implies that values in the language are based on objects rather than primitive types, i.e. that all values are "boxed".

The semantics of call by sharing differ from call by reference in that assignments to function arguments within the function aren't visible to the caller (unlike by reference semantics), so e.g. if a variable was passed, it is not possible to simulate an assignment on that variable in the caller's scope. However, since the function has access to the same object as the caller (no copy is made), mutations to those objects, if the objects are mutable, within the function are visible to the caller, which may appear to differ from call by value semantics. Mutations of a mutable object within the function are visible to the caller because the object is not copied or cloned — it is shared.

它是按值传递,其中所有值都是指向对象的指针。你可能认为这意味着你可以使用传入的指针来更改调用者的变量,使其按引用传递,但你不能,所以不是。

理解 Python 值传递如何工作的关键是知道没有 "unboxed"(非对象)值。整数是对象,"contains" 整数的变量实际上是指向存储在变量以外某处的整数对象的指针。浮点数、布尔值,同上。

变量在 Python 中并不是真正的 "hold" 值,就像它们在 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 从未更改为指向不同的对象,因此更改是对传入的列表进行的。名称 some_digits_of_pi 指的是与函数中被修改的对象相同的对象,因此调用者将看到他们的那个名称列表已被更改。

如果我们在函数末尾写 x = [2, 7, 1, 8, 2, 8, 1, 8],这将创建一个新的列表对象并将本地名称 x 指向它。它不会更改调用者变量指向的内容,因此该语句不会更改该列表。

换句话说,您不能使调用者的变量(在本例中为some_digits_of_pi)指向不同的对象。如果您将 x 更改为指向函数内的不同对象,则只有 x 指向该对象。

数字、字符串、元组等的工作方式完全相同。传入对象的指针;如果你改变函数内部参数的值,它会指向一个不同的对象,这自然不会改变调用者的变量。它只是 看起来 不同,因为这些类型的对象不是可变的,并且没有任何方法可以就地更改它们。

另一个令人困惑的地方是它看起来ints和lists都有+=运算符,但它确实发生了+=int 上做的事情与在 list 上做的相同操作非常不同。在列表中,+= return 是相同的列表对象(已修改),而在整数中,它可能 return 是一个完全不同的对象(因为整数是不可变的)。

通话的命名因通话对象而异。有一点绝对不是,不过是传递引用。

技术上和 according to the docs,python 都是按值传递。

[...] Otherwise, the value of the argument is placed in the slot, filling it (even if the expression is None, it fills the slot). When all arguments have been processed, the slots that are still unfilled are filled with the corresponding default value from the function definition. [...]

现在,正是因为这些值实际上是引用,这导致人们对约定进行争论并定义新名称以从更实际的角度描述正在发生的事情。按值引用调用、按共享调用、按句柄调用等都是人们可以使用的不同名称。

我一直不知道它的名字,但根据 Amadan 的说法,它是 "call by sharing"。我打个比方方便理解

假设我有一只狗,我给它取名 "Brutus"。我带布鲁图斯去散步,遇到了我的邻居山姆。山姆不喜欢我选的名字,叫我的狗 "Dodo"。如果他用刀刺向渡渡鸟,那当然会影响布鲁图斯,因为它们是同一只狗。假设他没有,而另一个邻居比利正带着他的狗出去散步。山姆改变主意,认为比利的狗是渡渡鸟。如果他用刀刺向渡渡鸟,不会影响布鲁图斯,因为它们是不同的狗。

你的功能类似。 mylist 走到您的函数,您的函数决定称他为 mylist,同名。然后您的函数决定 [1,2,3,4]mylist。您现在已经丢弃了原始列表并定义了一个新列表。您的重新分配不会影响原始列表。

Python,就我而言,就像 Java 一样,事实上所有函数(方法)都是按值传递的。参见 this infamous answer。 "call by sharing" 的缺点是它让你认为不同类型发生了不同的事情(即可变类型在函数调用中的行为与不可变类型不同)。事实并非如此,每次调用函数时都会发生同样的事情。另外,我读过的教科书都没有提到这个概念,我认为按值传递和按引用传递是更流行的术语。

按值传递如何工作?

它的工作原理是复制传递给函数的"value",然后再在函数中修改它。所以,举个例子:

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。这是因为它被复制了。我们将 return 处理这里的一些细微之处。忍耐一下。

按列表中的值传递

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应该没有变!!!

第一个例子太微妙了。我未能正确定义 "value" 被复制的内容。事实证明,复制的值不是数据,而是存储数据的地方。通常 C/C++ 程序员称其为 pointeraddress。让我们重新审视这些示例,但稍微修改一下以阐明要点。

值传递如何工作?版本 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"一样!但是在+=运算符之后,"value"改变了inside这个函数!但是,地址的更改没有传播到 外部 函数,因为 地址 按值传递 .函数内的新地址是 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 函数在适当的列表上运行,而不是 return 新列表。这就是为什么您会在函数外看到对 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的地址,但是我们在函数执行前复制了地址。 参数的地址是被复制的"value",不可能在函数内部改变。所以,这并没有改变主程序中my_list中的数据。

要点:Python 总是在其函数调用中使用按值传递语义。该值恰好是传入对象的地址,而不是传入对象的数据。