【问题标题】:Python functions call by referencePython 函数通过引用调用
【发布时间】:2012-11-08 23:01:21
【问题描述】:

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

示例 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 valuereference 参数而不是使用全球?

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

【问题讨论】:

  • 我找到的了解python调用模型的最佳资源是effbot上的这篇文章:effbot.org/zone/call-by-object.htm
  • 您应该阅读有关 Python 变量、可变和不可变对象的信息。同样在您的第一个示例中,为什么要更改 n,您正在使用新变量 square 来存储计算结果。
  • 请不要将两个不相关的问题合二为一。函数与过程的定义是定义问题,您的定义似乎偏向于 Pascal。在 Python 行话中没有过程之类的东西。
  • 另外,看看这个以前的 StackOverflow 答案。 stackoverflow.com/a/10262945/173292 相当直观地解释了对象引用模型的调用。
  • 另请参阅此 SO 问题以获得非常有用的解释:how do I pass a variable by reference

标签: python


【解决方案1】:

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

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

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(框),指向对象[0]

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

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

[0, 1]

注意:

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

例子:

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

程序的输出将是:

[0]

通过引用传递:

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

按值传递:

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

希望这会有所帮助。

【讨论】:

  • 你能像“通过对象引用”那样举出“按引用传递”和“按值传递”的例子吗?
  • @TJain 通过引用传递和通过值传递可以在 C++ 中看到。这个答案给出了很好的解释:stackoverflow.com/a/430958/5076583
  • 我仍然不明白这与传递引用有何不同。这个答案迫切需要一个例子来说明相同的伪代码如何为所有 3 个范例产生不同的输出。链接到仅解释传递引用和传递值之间的区别的不同答案无济于事。
  • 说“Python中的所有变量都是指针”和“函数调用中分配给参数的所有变量都分配给新的指针”不是更简单吗?如果您意识到这一点,您会发现 Python 就像 Java:它总是按值传递指针。见Kirk Strauser's answer
  • @MarcoSulla 没有。因为Python 没有指针。这是一种愚蠢的 Java 主义,这是因为 Java 具有“原始类型”和“引用类型”这一事实所必需的。不存在这样的区别。
【解决方案2】:

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

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

【讨论】:

  • 看看程序的结果会很有帮助。例如,'print x[0]' 返回 25 吗? (是的,但应该显示出来。)
  • @alexey clear_a 获得了对z 的引用,并将该引用存储在x 中。然后它立即更改x 以引用不同的数组。原来的z 数组在clear_a 的范围内被遗忘了,但实际上并没有改变。返回全局范围时它继续保持不变。与 clear_b 相比,它引用 z 然后直接对其进行操作,而无需创建新数组或以其他方式指向 x 和不同的东西。
  • 谢谢戴夫!我尝试在 clear_b、y = xy: y.pop() 中“创建一个新数组”,然后调用 clear_b(z),z 仍然被清空......所以我们需要类似 y = list(x) 的东西来创建 x 的副本(list(x ) 解释here)
  • 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
  • Note that in Python, you can also return more than one value, making some of the use cases for pass by reference less important:.. 不完全是,因为有时通过引用传递与内存管理有关,而不仅仅是更改参数值。有时你不希望你的参数被复制到例程中。特别是如果您的内存不足。
【解决方案3】:

希望下面的描述能很好地概括:

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

  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]

【讨论】:

  • 这是最好的总结! ^^^最好的总结在这里^^^每个人都读过这个答案! 1. 传递简单变量 2. 传递修改后的对象引用 3. 传递在函数中重新赋值的对象引用
  • 这个答案很混乱。 Python 不会以不同的方式传递不同类型的值:Python 中的所有函数参数都通过赋值接收它们的值,并且其行为方式与语言中其他任何地方的赋值行为完全相同。 (“虽然这种方式一开始可能并不明显,除非你是荷兰人。”)
【解决方案4】:

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

def foo(x):
    print x

bar = 'some value'
foo(bar)

因此,您正在创建一个值为“某个值”的字符串对象,并将其“绑定”到一个名为 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 以指向新对象”。在该行之后,有两个字符串对象:'some value'(bar 指向它)和'another value'(x 指向它)。

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

【讨论】:

  • 但是如果我想要函数来改变我的变量的内容呢?我认为没有办法做到这一点?
  • 您可以更改 name 指向的对象,例如如果该对象是可变的,则通过附加到列表中。没有 Python 语义可以说“更改调用我的范围的名称空间,以便我的参数列表中的名称现在指向与调用我时不同的东西”。
  • 实际上我要问的是是否有一种简单的方法可以让 python 不创建指向同一个对象的新指针,而是使用原始指针。我认为没有办法做到这一点。我从其他答案中得知,实现按引用传递的正常解决方法只是返回更多参数。如果是这样的话,我认为 a,b,c,d,e = foo(a,b,c,d,e) 比 foo(a,b,c, d,e)。
  • @aquirdturtle 这与创建新指针无关。您必须返回一个新对象,因为strings、tuples 和其他对象是不可变的。如果您传递一个可变对象,例如 list,您可以在函数内部更改它,而无需返回它,因为所有函数参数都是指向您传递的相同对象的指针。
  • @aquirdturtle Python 没有创建“新指针”... python 没有指针!
【解决方案5】:

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

【讨论】:

  • “不可变参数通过值有效传递:字符串、整数、元组都是通过引用传递”这句话自相矛盾
  • @eral,如果您了解完整的上下文,它并不自相矛盾。我对其进行了编辑以使其更清晰。
【解决方案6】:

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

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

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

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

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

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

【讨论】:

    【解决方案7】:

    Python 既不是按值传递也不是按引用传递。正如here 所述,它更像是“对象引用按值传递”

    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]

    更多信息请关注here

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

    from copy import copy
    
    def append(list):
      list2 = copy(list)
      list2.append(1)
      print list2
    
    list = [0]
    append(list)
    print list
    

    【讨论】:

      【解决方案8】:

      所以这有点微妙,因为虽然 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
      • 整个事情都涉及到列表和复制列表,这在某些方面可以回答,但我怎样才能做到这一点而不必将 int 转换为列表,或者这是我们拥有的最好的?跨度>
      • 您不能修改像 int 这样的原始参数。你必须把它放在某种容器中。我的例子把它放在一个列表中。你也可以把它放在一个类或字典里。但实际上,您应该在函数之外重新分配它,例如 x = foo(x)
      【解决方案9】:

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

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

      【讨论】:

      • 这完全不正确。 Python 总是通过“对象引用”(与变量引用不同)传递。
      • 我的意思是类比可变对象——通过引用传递和不可变对象——通过值传递。我所说的完全正确。你可能没有听懂我在说什么
      • 我抓住了它,它是错误的。它与对象是可变还是不可变无关;它总是以同样的方式完成。
      【解决方案10】:

      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
      

      【讨论】:

      • 不,这不是真的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))
      【解决方案11】:

      给出的答案是

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

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

      【讨论】:

        【解决方案12】:
        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)
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-09-24
          • 2013-04-29
          • 2015-05-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多