99

在某些语言中,您可以使用特殊的保留字(如refval )通过引用或值传递参数。当您将参数传递给 Python 函数时,它永远不会在离开函数时改变参数的值。做到这一点的唯一方法是使用全局保留字(或者按照我目前的理解)。

示例 1:

k = 2

def foo (n):
     n = n * n     #clarity regarding comment below
     square = n
     return square

j = foo(k)
print j
print k

会显示

>>4
>>2

显示 k 不变。

在这个例子中,变量 n 永远不会改变

示例 2:

n = 0
def foo():
    global n
    n = n * n
    return n

在此示例中,变量 n 已更改。

Python中有什么方法可以调用函数并告诉Python该参数参数还是引用参数而不是使用全局参数?

其次,在剑桥 A 级考试中,他们现在说函数返回单个值,而过程返回多个值。在 80 年代,我被教导一个函数有一个返回语句而过程没有。为什么现在不正确?

4

12 回答 12

204

基本上有三种“函数调用”:

  • 按值传递
  • 通过引用传递
  • 通过对象引用传递

Python 是一种 PASS-BY-OBJECT-REFERENCE 编程语言。

首先,重要的是要了解变量和变量(对象)的值是两个独立的东西。变量“指向”对象。变量不是对象。再次:

变量不是对象

示例:在以下代码行中:

>>> x = []

[]是空列表,x是指向空列表的变量,但x本身不是空列表。

将变量 ( x,在上例中) 视为一个框,将变量 ( []) 的“值”视为框内的对象。

按对象引用传递(python 中的案例):

这里,“对象引用是按值传递的”。

def append_one(li):
    li.append(1)
x = [0]
append_one(x)
print x

在这里,该语句创建了一个指向 objectx = [0]的变量(框) 。x[0]

在调用函数时,li会创建一个新框。的内容li与盒子的内容相同x两个盒子都包含相同的对象。也就是说,两个变量都指向内存中的同一个对象。因此,对 所指向的对象的任何更改li也将由 所指向的对象反映出来x

总之,上述程序的输出将是:

[0, 1]

笔记:

如果变量li在函数中被重新赋值,那么li将指向内存中的一个单独的对象。x但是,它将继续指向它之前指向的内存中的同一个对象。

例子:

def append_one(li):
    li = [0, 1]
x = [0]
append_one(x)
print x

该程序的输出将是:

[0]

通过参考:

来自调用函数的盒子被传递给被调用函数。隐含地,盒子的内容(变量的值)被传递给被调用的函数。因此,被调用函数中对框内容的任何更改都将反映在调用函数中。

按价值传递:

在被调用函数中创建一个新盒子,并将来自调用函数的盒子内容的副本存储到新盒子中。

希望这可以帮助。

于 2015-10-11T15:15:25.417 回答
86

您不能在 Python 中的函数内更改不可变对象,例如stror tuple,但您可以执行以下操作:

def foo(y):
  y[0] = y[0]**2

x = [5]
foo(x)
print x[0]  # prints 25

然而,这是一种奇怪的方法,除非您总是需要对数组中的某些元素进行平方。

请注意,在 Python 中,您还可以返回多个值,从而使某些通过引用传递的用例变得不那么重要:

def foo(x, y):
   return x**2, y**2

a = 2
b = 3
a, b = foo(a, b)  # a == 4; b == 9

当您返回这样的值时,它们将作为元组返回,而元组又被解包。

编辑: 另一种思考方式是,虽然您不能在 Python 中通过引用显式传递变量,但您可以修改传入的对象的属性。在我的示例(和其他示例)中,您可以修改列表的成员那是传入的。但是,您将无法完全重新分配传入的变量。例如,看下面的两段代码,它们看起来可能会做类似的事情,但最终会得到不同的结果:

def clear_a(x):
  x = []

def clear_b(x):
  while x: x.pop()

z = [1,2,3]
clear_a(z) # z will not be changed
clear_b(z) # z will be emptied
于 2012-11-08T23:13:03.187 回答
30

好的,我会尝试一下。Python 通过对象引用传递,这与您通常认为的“按引用”或“按值”不同。举个例子:

def foo(x):
    print x

bar = 'some value'
foo(bar)

因此,您正在创建一个值为 'some value' 的字符串对象,并将其“绑定”到一个名为bar. 在 C 中,这类似于bar指向“某个值”的指针。

当你打电话时foo(bar),你并没有通过bar自己。您正在传递bar' 值:指向“某个值”的指针。此时,有两个“指针”指向同一个字符串对象。

现在将其与:

def foo(x):
    x = 'another value'
    print x

bar = 'some value'
foo(bar)

这就是区别所在。在行中:

x = 'another value'

你实际上并没有改变x. 事实上,这甚至是不可能的。相反,您正在创建一个新的字符串对象,其值为“另一个值”。那个赋值运算符?这并不是说“x用新值覆盖指向的东西”。它的意思是“更新x以指向新对象”。在该行之后,有两个字符串对象:“某个值”(bar指向它)和“另一个值”(x指向它)。

这并不笨拙。当您了解它的工作原理时,它就是一个精美优雅、高效的系统。

于 2012-11-09T00:35:47.370 回答
28

希望下面的描述能很好地总结它:

这里有两件事要考虑 - 变量和对象。

  1. 如果您正在传递一个变量,那么它是按值传递的,这意味着对函数内变量所做的更改对该函数是本地的,因此不会在全局范围内反映出来。这更像是一种类似“C”的行为。

例子:

def changeval( myvar ):
   myvar = 20; 
   print "values inside the function: ", myvar
   return

myvar = 10;
changeval( myvar );
print "values outside the function: ", myvar

输出/输出:

values inside the function:  20 
values outside the function:  10
  1. 如果您传递封装在可变对象(如列表)中的变量,那么只要不重新分配对象,对对象所做的更改就会全局反映。

例子:

def changelist( mylist ):
   mylist2=['a'];
   mylist.append(mylist2);
   print "values inside the function: ", mylist
   return

mylist = [1,2,3];
changelist( mylist );
print "values outside the function: ", mylist

输出/输出:

values inside the function:  [1, 2, 3, ['a']]
values outside the function:  [1, 2, 3, ['a']]
  1. 现在考虑重新分配对象的情况。在这种情况下,对象指的是发生这种情况的函数的本地新内存位置,因此不会全局反映。

例子:

def changelist( mylist ):
   mylist=['a'];
   print "values inside the function: ", mylist
   return

mylist = [1,2,3];
changelist( mylist );
print "values outside the function: ", mylist

输出/输出:

values inside the function:  ['a']
values outside the function:  [1, 2, 3]
于 2013-06-03T09:15:36.703 回答
10

Python 既不是按值传递也不是按引用传递。它更多的是“对象引用按值传递” 如下所述:

  1. 这就是为什么它不是按值传递的原因。 因为

    def append(list):
        list.append(1)
    
    list = [0]
    reassign(list)
    append(list)
    

返回 [0,1] 表明某种引用被清楚地传递为按值传递不允许函数根本改变父范围

看起来像通过引用传递,嗯?没有。

  1. 这就是为什么它不是按引用传递的原因。因为

    def reassign(list):
      list = [0, 1]
    
    list = [0]
    reassign(list)
    print list
    

返回 [0] 表明在重新分配列表时原始引用已被破坏。 引用传递将返回 [0,1]

更多信息请看这里

如果您希望您的函数不在作用域之外进行操作,您需要制作一个用于创建新对象的输入参数的副本。

from copy import copy

def append(list):
  list2 = copy(list)
  list2.append(1)
  print list2

list = [0]
append(list)
print list
于 2015-07-22T18:21:59.890 回答
10

从技术上讲,python 不按值传递参数:全部通过引用。但是......由于python有两种类型的对象:不可变和可变,这就是发生的事情:

  • 不可变参数通过值有效传递:字符串、整数、元组都是不可变对象类型。虽然它们在技术上是“通过引用传递”(就像所有参数一样),但由于您无法在函数内部就地更改它们,因此它看起来/表现得好像它是按值传递的。

  • 可变参数通过引用有效地传递:列表或字典通过其指针传递。函数内部的任何就地更改(如(追加或删除))都会影响原始对象。

这就是 Python 的设计方式:没有副本,全部通过引用传递。您可以显式传递副本。

def sort(array):
    # do sort
    return array

data = [1, 2, 3]
sort(data[:]) # here you passed a copy

最后一点我想提一下哪个函数有它自己的作用域。

def do_any_stuff_to_these_objects(a, b): 
    a = a * 2 
    del b['last_name']

number = 1 # immutable
hashmap = {'first_name' : 'john', 'last_name': 'legend'} # mutable
do_any_stuff_to_these_objects(number, hashmap) 
print(number) # 1 , oh  it should be 2 ! no a is changed inisde the function scope
print(hashmap) # {'first_name': 'john'}
于 2020-04-01T20:16:37.243 回答
3

所以这有点微妙,因为虽然 Python 只按值传递变量,但 Python 中的每个变量都是一个引用。如果您希望能够通过函数调用来更改您的值,那么您需要一个可变对象。例如:

l = [0]

def set_3(x):
    x[0] = 3

set_3(l)
print(l[0])

在上面的代码中,函数修改了一个 List 对象(它是可变的)的内容,所以输出是 3 而不是 0。

我写这个答案只是为了说明 Python 中“按值”的含义。上面的代码风格不好,如果你真的想改变你的值,你应该编写一个类并在该类中调用方法,正如 MPX 建议的那样。

于 2012-11-08T23:13:49.020 回答
1

考虑变量是一个盒子,它指向的值是盒子里面的“东西”:

1.通过引用传递:函数共享同一个盒子,因此里面的东西也一样。

2.按值传递:函数创建一个新盒子,一个旧盒子的复制品,包括里面任何东西的副本。例如。Java - 函数创建盒子的副本和里面的东西,可以是:原始/对对象的引用。(请注意,新框中复制的引用和原始引用仍然指向同一个对象,这里的引用是框内的东西,而不是它指向的对象)

3. 通过对象引用传递:该函数创建一个盒子,但它包含与初始盒子所包含的相同的东西。所以在Python中:

a)如果所述框内的东西是可变的,所做的更改将反映在原始框(例如列表)中

b) 如果事物是​​不可变的(如 python 字符串和数字类型),那么函数内的框将包含相同的事物,直到您尝试更改其值。一旦改变,函数框中的东西与原来的相比是一个全新的东西。因此,该盒子的 id() 现在将给出它所包含的新事物的身份

于 2020-02-20T05:16:46.330 回答
0

给出的答案是

def set_4(x):
   y = []
   for i in x:
       y.append(i)
   y[0] = 4
   return y

l = [0]

def set_3(x):
     x[0] = 3

set_3(l)
print(l[0])

就它在问题中所说的而言,这是最好的答案。但是,与 VB 或 Pascal 相比,这似乎是一种非常笨拙的方法。这是我们拥有的最好的方法吗?

它不仅笨拙,还涉及以某种方式手动改变原始参数,例如通过将原始参数更改为列表:或将其复制到另一个列表,而不仅仅是说:“将此参数用作值”或“使用此参数”作为参考”。简单的答案可能是没有保留字,但这些都是很好的解决方法吗?

于 2012-11-08T23:53:37.747 回答
0
class demoClass:
    x = 4
    y = 3
foo1 = demoClass()
foo1.x = 2
foo2 = demoClass()
foo2.y = 5
def mySquare(myObj):
    myObj.x = myObj.x**2
    myObj.y = myObj.y**2
print('foo1.x =', foo1.x)
print('foo1.y =', foo1.y)
print('foo2.x =', foo2.x)
print('foo2.y =', foo2.y)
mySquare(foo1)
mySquare(foo2)
print('After square:')
print('foo1.x =', foo1.x)
print('foo1.y =', foo1.y)
print('foo2.x =', foo2.x)
print('foo2.y =', foo2.y)
于 2020-08-24T19:59:43.163 回答
-2

在 Python 中,按引用或按值传递与您传递的实际对象有关。因此,例如,如果您传递一个列表,那么您实际上是按引用传递,因为列表是一个可变对象。因此,您正在传递一个指向函数的指针,并且您可以修改函数体中的对象(列表)。

当您传递一个字符串时,此传递是按值完成的,因此正在创建一个新的字符串对象,并且当函数终止时它被销毁。所以这一切都与可变和不可变对象有关。

于 2012-11-08T23:12:48.743 回答
-2

Python 已经被 ref 调用了。

让我们举个例子:

  def foo(var):
      print(hex(id(var)))


  x = 1 # any value
  print(hex(id(x))) # I think the id() give the ref... 
  foo(x)

输出

  0x50d43700 #with you might give another hex number deppend on your memory 
  0x50d43700
于 2017-06-06T22:12:29.220 回答