Python函数通过引用调用


82

在某些语言中,可以使用特殊的保留字(例如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该参数还是引用参数,而不是使用global?

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


5
我发现的用于理解python调用模型的最佳资源是effbot上的这篇文章:effbot.org/zone/call-by-object.htm
Wilduck,2012年

1
您应该阅读有关Python变量,可变和不可变对象的信息。同样在第一个示例中,为什么n要更改原因,您使用的是新变量square来存储计算结果。
法肯多·卡斯科

6
请不要将两个不相关的问题合而为一。函数与过程的定义是一个定义问题,看来您的定义偏向于Pascal。Python行话中没有像过程这样的东西。
弗雷德·富

2
另外,请查看此以前的StackOverflow答案。stackoverflow.com/a/10262945/173292它相当直观地解释了对象引用模型的调用。
Wilduck

1
另请参见此SO问题以获取非常有用的解释:我如何通过引用传递变量
Michael Ohlrogge

Answers:


78

您无法在Python函数中更改不可变的对象(例如str或)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

2
查看程序结果将很有帮助。例如,“ print x [0]”是否返回25?(是的,但是应该显示出来。)
Ray Salemi

11
@alexeyclear_a被引用,z并将其存储在中x。然后,它立即更改x为引用其他数组。原始z数组在的范围内被遗忘clear_a,但实际上并未更改。返回到全局范围时,它保持不变。与之对照的clear_b是引用该引用z然后直接对其进行操作的对象,而无需创建新数组或以其他方式指向x和其他对象。
dave mankoff

谢谢戴夫!我尝试在clear_b中“创建一个新数组” y = xy: y.pop()然后调用clear_b(z),z仍然被清空...因此,我们需要y = list(x)创建x的副本(此处解释list(x))
alexey

run_thread = [True] t= threading.Thread(target=module2.some_method, \ args=(11,1,run_thread)) --do something - and when you want to sop run_thread[0] =False
Alex Punnen

与Java不同,Python中没有原始类型。所有类型的实例对象。
Marco Sulla

181

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

  • 传递价值
  • 通过参考
  • 通过对象引用

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

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

变量不是对象

示例:在下面的代码行中:

>>> x = []

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

将变量(x在上述情况下)视为一个框,并将变量([])的“值”作为该框内的对象。

通过对象引用(在python中为例):

此处,“对象引用按值传递”。

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

在这里,该语句生成x = [0]一个x指向object的变量(框)[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]

通过引用:

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

通过值:

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

希望这可以帮助。


3
您能否像“按对象引用传递”一样,给出“按引用传递”和“按值传递”的示例。
TJain

@TJain在C ++中可以看到按引用传递和按值传递。这个答案给出了很好的解释:stackoverflow.com/a/430958/5076583
Shobhit Verma

3
我仍然不明白这与通过引用有何不同。这个答案非常需要一个示例,说明相同的伪代码将如何为所有3个范例产生不同的输出。链接到仅解释按引用传递和按值传递之间的区别的其他答案无济于事。
Jess Riedel

说“ Python中的所有变量都是指针”和“在函数调用中分配给参数的所有变量都分配给新的指针” ,不是更简单吗?如果意识到这一点,就会发现Python就像Java:它总是总是按value传递指针。参见柯克·斯特拉瑟(Kirk Strauser)的答案
Marco Sulla

26

好吧,我将对此采取行动。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指向它)。

这并不笨拙。当您了解它是如何工作的时,它就是一个优美而高效的系统。


3
但是,如果我希望函数更改变量的内容怎么办?我认为没有办法做到这一点?
aquirdturtle,2016年

1
您可以更改名称所指向的对象,例如,如果该对象是可变的,则可以附加到列表。没有Python语义可以说“更改调用我的作用域的名称空间,以便我的参数列表中的名称现在指向与调用它时不同的名称”。
柯克·斯特拉瑟

2
实际上,我要问的是,是否有一种简单的方法来使python不创建指向同一对象的新指针,而是使用原始指针。我认为没有办法做到这一点。我从其他答案中得出,实现按引用传递功能的正常解决方法是返回更多参数。如果是这种情况,我会在参数a,b,c,d,e = foo(a,b,c,d,e)比foo(a,b,c, d,e)。
aquirdturtle,2016年

2
@aquirdturtle这与创建新指针无关。您必须返回一个新对象,因为strings,tuples和其他对象是不可变的。如果传递一个可变对象(如)list,则可以在函数内部对其进行更改,而无需返回它,因为所有函数参数都是指向您传递的相同对象的指针。
Marco Sulla

23

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

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

  1. 如果要传递变量,则按值传递,这意味着对该函数内的变量所做的更改是该函数的局部变量,因此不会在全局范围内反映出来。这更多的是类似于“ C”的行为。

例:

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

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

O / P:

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

O / P:

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

O / P:

values inside the function:  ['a']
values outside the function:  [1, 2, 3]

2
这是最好的总结!^^^最佳摘要在这里^^^每个人都阅读了这个答案!1.传递简单变量2.传递经过修改的对象引用3.传递在函数中重新分配的对象引用
gaoithe 17-10-25

3
这个答案很困惑。Python不会传递不同类型的值:Python中的所有函数参数都通过赋值接收它们的值,并且行为与赋值在语言中其他位置的行为完全相同。(“尽管起初除非您是荷兰人,否则这种方式可能并不明显。”)
Daniel Pryden

9

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

4

从技术上讲,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'}

1
“不变的参数有效地通过值传递:字符串,整数,元组都通过引用传递”这句话有自相矛盾之处
eral

@eral,如果您了解完整的上下文,则它本身并不矛盾。我对其进行了编辑以使其更加清晰。
Watki02

2

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

l = [0]

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

set_3(l)
print(l[0])

在上面的代码中,该函数修改了List对象的内容(可变的),因此输出为3而不是0。

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


这回答了一半的问题,那么我将如何编写相同的set_3(x)函数,以使其不更改l的值,但返回已更改的新列表?
蒂莫西·劳曼

只需创建一个新列表并对其进行修改:y = [a for a in x]; y[0] = 3; return y
Isaac 2012年

整个过程都涉及到列表和复制列表,这在某种程度上可以回答问题,但是我如何在不必将int转换为列表的情况下做到这一点,或者这是我们最好的方法?
蒂莫西·劳曼

您不能修改像int这样的原始参数。您必须将其放入某种容器中。我的示例将其放在列表中。您也可以将其放在班级或字典中。确实,您应该使用类似的功能在函数外部重新分配它x = foo(x)
艾萨克(Isaac)

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相比,它似乎很笨拙。这是我们拥有的最好的方法吗?

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


0

考虑该变量是一个框,它指向的值是框内的“事物”:

1.通过引用:函数共享同一框,从而也包含其中的东西。

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

3.通过对象引用传递:该函数创建一个框,但是它与初始框所包含的内容相同。因此在Python中

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

b)如果事物是不可变的(例如python字符串和数字类型),则函数内部的框将保留相同的事物,直到您尝试更改其值。一旦更改,与原始功能相比,功能框中的内容是全新的。因此,该盒子的id()现在将给出它所包围的新事物的标识


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)

-2

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

当传递字符串时,该传递是通过值完成的,因此将创建一个新的字符串对象,并且在函数终止时将其销毁。因此,所有这些都与可变和不可变的对象有关。


1
那是完全不对的。Python始终通过“对象引用”(不同于变量引用)传递。
Kirk Strauser 2012年

我的意思是类比可变对象-通过引用和不可变对象-通过值。这是我所说的完全正确的方法。您可能没有听懂我在说什么
jkalivas

1
我抓住了,这是错误的。它与对象是可变的还是不可变的无关;它总是以相同的方式进行。
Kirk Strauser 2012年

-2

Python已通过引用调用。

让我们举个例子:

  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

不,这不是真的def foo(var): print(id(var)) var = 1234 print(id(var)) x = 123 print(id(x)) # I think the id() give the ref... foo(x) print(id(x))
Zohaib Ijaz
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.