在Python中通过引用传递整数

138

如何在Python中通过引用传递整数?

我想修改我传递给函数的变量的值。我已经阅读过Python中的所有内容都是按值传递,但肯定有一个简单的技巧。例如,在Java中,您可以传递IntegerLong等引用类型。

  1. 如何通过引用将整数传递到函数中?
  2. 最佳实践是什么?

请参考以下链接,了解一种不错但略显复杂的方法,使用匿名类(可变)将整数包装成“引用”:https://dev59.com/2XNA5IYBdhLWcg3wBo9m#1123054,例如:ref = type('', (), {'n':1}) - robert
13个回答

158
在Python中,它不完全是这样工作的。Python传递对象的引用。在您的函数内部,您有一个对象--如果可能的话,您可以自由地改变该对象。然而,整数是不可变的。一种解决方法是将整数放入可以被改变的容器中进行传递:
def change(x):
    x[0] = 3

x = [1]
change(x)
print x

这最多算是笨拙的,但在Python中你不可能做得更好。原因是因为在Python中,赋值(=)将右侧的对象绑定到左侧的任何内容(或传递给适当的函数)。
了解这一点,我们可以看到为什么没有办法在函数内部更改不可变对象的值——您无法更改其任何属性,因为它是不可变的,您也不能只分配一个新值给“变量”,因为那样实际上是创建一个新对象(与旧对象不同),并将其命名为本地命名空间中旧对象的名称。
通常的解决方法是简单地返回您想要的对象:
def multiply_by_2(x):
    return 2*x

x = 1
x = multiply_by_2(x)

在上述第一个示例中,实际上将3传递给了x.__setitem__

9
术语让我们感到困惑并不奇怪。这里它被描述为“按对象调用(call-by-object)”,而这里它被描述为“按值传递(pass-by-value)”。在其他地方,它被称为“按引用传递”并带有一个星号表示其实际意义...基本上,问题在于社区还没有弄清楚如何称呼它。 - mgilson
4
Python的引用和Java的引用完全相同。而Java的引用是根据JLS指向对象的指针来定义的。在Java/Python引用和C++对象指针之间,从语义上讲基本上存在一种完整的双射,没有其他东西可以像它描述这种语义一样好。“它们不必进行解引用”嗯,点运算符只是对左侧进行了解引用操作。运算符在不同的编程语言中可能会有所不同。 - user102008
1
将标量按引用传递的支持,有助于实现跨语言兼容性(例如将C++转换为Python)。 - user2585501
@mgilson 我认为,例如数字并非真正通过引用传递,除了可能需要占用多个机器字的大整数。那将非常低效。在C#中,当您需要将一个数字(或任何其他“值类型”)作为对象处理(例如调用Int32类型的方法之一),它被“装箱”,即在堆上分配并替换为对该副本的引用。其余时间,其值是直接复制的。 - Ron Inbar
2
@RonInbar -- 我不太确定你在这里想表达什么。Python并不像C#(或Java)一样具有装箱和拆箱的概念。在参考实现中,_everything_只是一个PyObject。当您调用python函数时,python不知道(也不关心)正在传递的类型。实际上,您可以多次使用不同类型调用相同的函数(考虑内置的str)。我认为,在函数接口层面上没有针对原语的特殊情况。是的,这是python函数调用比较昂贵的原因之一。 - mgilson
显示剩余3条评论

35
大多数需要传递引用的情况是为了将多个值返回给调用者。在Python中,使用多个返回值是一种“最佳实践”,这比像Java这样的语言要容易得多。
以下是一个简单的例子:
def RectToPolar(x, y):
    r = (x ** 2 + y ** 2) ** 0.5
    theta = math.atan2(y, x)
    return r, theta # return 2 things at once

r, theta = RectToPolar(3, 4) # assign 2 things at once

19

不是直接传递一个值,而是像传递了一个值一样使用它。

x = 7
def my_method():
    nonlocal x
    x += 1
my_method()
print(x) # 8

注意事项:

  • nonlocal 是在 Python 3 中引入的。
  • 如果封闭作用域是全局作用域,请使用 global 而不是 nonlocal

但是您需要仅使用一个特定的变量名称来完成此操作。如果它是确切已知的引用,这将减少“按引用传递”的兴趣。 - NeronLeVelu

13
也许这不是pythonic的方式,但你可以这样做。
import ctypes

def incr(a):
    a += 1

x = ctypes.c_int(1) # create c-var
incr(ctypes.ctypes.byref(x)) # passing by ref

9

实际上,最好的做法是退一步并问问自己是否真的需要这样做。 为什么你想修改传递给函数的变量的值?

如果你需要为了快速解决问题而这样做,最快的方法是传递一个包含整数的list,并在每次使用它时添加[0],就像mgilson的答案所示。

如果您需要为某些更重要的事情这样做,请编写一个具有int属性的class,以便您可以轻松设置它。当然,这迫使您想出一个好的类名和属性名称-如果您想不出任何东西,请再次阅读几遍该句子,然后使用list

更一般地说,如果您试图直接将某些Java习惯用法移植到Python,则您正在错误地这样做。即使存在一些直接对应的内容(如static/@staticmethod),在大多数Python程序中,您仍然不希望使用它,就像您在Java中使用它一样。


JAVA不通过引用传递整数(整数或任何对象。装箱对象在赋值时也会被替换)。 - Luis Masuelli
@LuisMasuelli 好的,包装的原始类型与对象一样处理,阻止其像 OP 想要的那样使用的唯一因素是箱子是不可变的(并且由于本身的基元也是不可变的,整个事情都是不可变的,并且只能在变量级别进行更改) - Kroltan
1
一个很好的使用案例是在递归函数内计算调用次数(总数,而不是深度)。您需要一个可以在所有分支调用中递增的数字。标准整数就不够用了。 - z33k

6

Numpy单元素数组是可变的,但对于大多数情况来说,它可以像一个python数字变量一样进行评估。因此,它比单元素列表更方便作为引用类型的数字容器。

    import numpy as np
    def triple_var_by_ref(x):
        x[0]=x[0]*3
    a=np.array([2])
    triple_var_by_ref(a)
    print(a+1)

输出:

7

6

正确的答案是使用类并将值放在类内,这样可以按照您所需的方式通过引用传递。

class Thing:
  def __init__(self,a):
    self.a = a
def dosomething(ref)
  ref.a += 1

t = Thing(3)
dosomething(t)
print("T is now",t.a)


是的,编写一个简单的包装类并始终访问内部对象是正确的方法。 - Smiley1000

6
也许比只有一个元素列表的技巧更易于自我记录的方法是旧的空类型技巧:
def inc_i(v):
    v.i += 1

x = type('', (), {})()
x.i = 7
inc_i(x)
print(x.i)

或者将数字封装在一个类中:https://github.com/markrages/python_mutable_number - markrages

3
class PassByReference:
    def Change(self, var):
        self.a = var
        print(self.a)
s=PassByReference()
s.Change(5)     

3
在Python中,每个值都是一个引用(指向对象的指针),就像Java中的非原始类型一样。此外,与Java一样,Python只支持按值传递参数。因此,从语义上讲,它们几乎是相同的。
既然你在问题中提到了Java,我想看看你如何在Java中实现你想要的。如果你能展示它在Java中怎么做,我可以告诉你如何在Python中完全等效地实现它。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接