代码之家  ›  专栏  ›  技术社区  ›  David Sykes

如何通过引用传递变量?

  •  2235
  • David Sykes  · 技术社区  · 16 年前

    python文档似乎不清楚参数是通过引用还是通过值传递的,下面的代码生成的值“original”不变。

    class PassByReference:
        def __init__(self):
            self.variable = 'Original'
            self.change(self.variable)
            print(self.variable)
    
        def change(self, var):
            var = 'Changed'
    

    我可以通过实际引用传递变量吗?

    24 回复  |  直到 6 年前
        1
  •  2412
  •   random_user    8 年前

    论点是 passed by assignment . 这背后的理由是双重的:

    1. 传入的参数实际上是 参考 对象(但引用是通过值传递的)
    2. 有些数据类型是可变的,但另一些数据类型不是可变的

    所以:

    • 如果你通过 易变的 对象变为一个方法,该方法获取对同一对象的引用,您可以将其转换为您内心的喜悦,但是如果您在该方法中重新绑定引用,外部作用域将对此一无所知,完成后,外部引用仍将指向原始对象。

    • 如果你通过 不变的 对象到方法,您仍然不能重新绑定外部引用,甚至不能改变对象。

    为了更清楚地说明这一点,我们来举几个例子。

    列表-可变类型

    让我们尝试修改传递给方法的列表:

    def try_to_change_list_contents(the_list):
        print('got', the_list)
        the_list.append('four')
        print('changed to', the_list)
    
    outer_list = ['one', 'two', 'three']
    
    print('before, outer_list =', outer_list)
    try_to_change_list_contents(outer_list)
    print('after, outer_list =', outer_list)
    

    输出:

    before, outer_list = ['one', 'two', 'three']
    got ['one', 'two', 'three']
    changed to ['one', 'two', 'three', 'four']
    after, outer_list = ['one', 'two', 'three', 'four']
    

    因为传入的参数是对 outer_list ,而不是它的副本,我们可以使用可变列表方法来更改它,并将更改反映在外部范围中。

    现在让我们看看当我们试图更改作为参数传入的引用时会发生什么:

    def try_to_change_list_reference(the_list):
        print('got', the_list)
        the_list = ['and', 'we', 'can', 'not', 'lie']
        print('set to', the_list)
    
    outer_list = ['we', 'like', 'proper', 'English']
    
    print('before, outer_list =', outer_list)
    try_to_change_list_reference(outer_list)
    print('after, outer_list =', outer_list)
    

    输出:

    before, outer_list = ['we', 'like', 'proper', 'English']
    got ['we', 'like', 'proper', 'English']
    set to ['and', 'we', 'can', 'not', 'lie']
    after, outer_list = ['we', 'like', 'proper', 'English']
    

    自从 the_list 参数是按值传递的,将新列表赋给它对方法外部的代码看不到任何影响。这个 第一览表 外列表 参考,我们有 第一览表 指向新列表,但无法更改 外列表 指出。

    字符串-不可变类型

    它是不可变的,所以我们无法更改字符串的内容。

    现在,让我们尝试更改引用

    def try_to_change_string_reference(the_string):
        print('got', the_string)
        the_string = 'In a kingdom by the sea'
        print('set to', the_string)
    
    outer_string = 'It was many and many a year ago'
    
    print('before, outer_string =', outer_string)
    try_to_change_string_reference(outer_string)
    print('after, outer_string =', outer_string)
    

    输出:

    before, outer_string = It was many and many a year ago
    got It was many and many a year ago
    set to In a kingdom by the sea
    after, outer_string = It was many and many a year ago
    

    又一次,自从 the_string 参数是按值传递的,将新字符串赋给它对方法外部的代码看不到任何影响。这个 弦乐 outer_string 参考,我们有 弦乐 指向新字符串,但无法更改 外串 指出。

    我希望这能把事情弄清楚一点。

    编辑: 有人指出,这并不能回答@david最初提出的问题,“我是否可以通过实际引用传递变量?”我们来研究一下。

    我们怎么解决这个问题?

    正如@andera的答案所示,您可以返回新值。这不会改变传递信息的方式,但会让您获得想要返回的信息:

    def return_a_whole_new_string(the_string):
        new_string = something_to_do_with_the_old_string(the_string)
        return new_string
    
    # then you could call it like
    my_string = return_a_whole_new_string(my_string)
    

    如果您真的想避免使用返回值,可以创建一个类来保存您的值并将其传递到函数中,或者使用现有的类,如列表:

    def use_a_wrapper_to_simulate_pass_by_reference(stuff_to_change):
        new_string = something_to_do_with_the_old_string(stuff_to_change[0])
        stuff_to_change[0] = new_string
    
    # then you could call it like
    wrapper = [my_string]
    use_a_wrapper_to_simulate_pass_by_reference(wrapper)
    
    do_something_with(wrapper[0])
    

    虽然这看起来有点麻烦。

        2
  •  581
  •   random_user    8 年前

    问题来自对Python中的变量的误解。如果你习惯了大多数的传统语言,你就有了一个心理模型,可以按照以下顺序进行:

    a = 1
    a = 2
    

    你相信吗? a 是存储值的内存位置 1 ,然后更新以存储值 2 . 这不是Python中的工作方式。更确切地说, 以对具有值的对象的引用开始 ,然后作为对具有值的对象的引用重新分配 . 这两个物体可能会继续共存,即使 不再引用第一个引用;事实上,它们可能由程序中的任何其他引用共享。

    当使用参数调用函数时,将创建一个引用传入对象的新引用。这与函数调用中使用的引用不同,因此无法更新该引用并使其引用新对象。在您的示例中:

    def __init__(self):
        self.variable = 'Original'
        self.Change(self.variable)
    
    def Change(self, var):
        var = 'Changed'
    

    self.variable 是对字符串对象的引用 'Original' . 当你打电话 Change 创建第二个引用 var 对象。在函数内部重新分配引用 var 到其他字符串对象 'Changed' 但是参考文献 自变量 是分开的,不会改变。

    唯一的方法是传递一个可变的对象。因为这两个引用引用同一个对象,所以对该对象的任何更改都会反映在这两个地方。

    def __init__(self):         
        self.variable = ['Original']
        self.Change(self.variable)
    
    def Change(self, var):
        var[0] = 'Changed'
    
        3
  •  268
  •   Zenadix    9 年前

    我发现其他的答案相当长和复杂,所以我创建了这个简单的图来解释Python处理变量和参数的方式。 enter image description here

        4
  •  224
  •   Honest Abe Cody Piersall    12 年前

    它既不是传递值也不是传递引用-它是由对象调用的。见Fredrik Lundh:

    http://effbot.org/zone/call-by-object.htm

    这是一个重要的引述:

    “…变量[名称]是 对象;它们不能由其他变量表示或由对象引用。”

    在您的示例中,当 Change 方法被调用--a namespace 为其创建;以及 var 在该命名空间中成为字符串对象的名称 'Original' . 然后,该对象在两个名称空间中有一个名称。下一步, var = 'Changed' 绑定 var 到一个新的字符串对象,因此该方法的命名空间忘记了 “原创” . 最后,该名称空间被遗忘,字符串 'Changed' 随之而来的。

        5
  •  150
  •   Daren Thomas    16 年前

    想想被传递的东西 按赋值 而不是通过引用/值。这样,只要你理解在正常作业中发生的事情,一切都很清楚。

    因此,当将列表传递给函数/方法时,该列表被分配给参数名。附加到列表将导致列表被修改。重新分配列表 里面 函数不会更改原始列表,因为:

    a = [1, 2, 3]
    b = a
    b.append(4)
    b = ['a', 'b']
    print a, b      # prints [1, 2, 3, 4] ['a', 'b']
    

    由于不能修改不可变类型,因此它们 似乎 就像通过值传递-将int传递给函数意味着将int分配给函数参数。您只能重新分配它,但它不会改变原始变量的值。

        6
  •  57
  •   Raymond Hettinger    12 年前

    effbot(又名fredrik lundh)将python的变量传递样式描述为按对象调用: http://effbot.org/zone/call-by-object.htm

    对象在堆上分配,指向它们的指针可以在任何地方传递。

    • 当你做一个任务时,比如 x = 1000 创建一个字典条目,将当前命名空间中的字符串“x”映射到包含1000的整数对象的指针。

    • 当您更新“x”时 x = 2000 ,将创建一个新的整数对象,并更新字典以指向新对象。旧的一千个对象是不变的(可能还活着,也可能不活着,这取决于是否有其他的对象)。

    • 当你做一个新的任务,比如 y = x ,将创建一个新的字典条目“Y”,该条目指向与“X”条目相同的对象。

    • 像字符串和整数这样的对象是 不变的 . 这仅仅意味着在创建对象之后,没有方法可以更改它。例如,一旦创建了整数对象1000,它将永远不会改变。数学是通过创建新的整数对象来完成的。

    • 像列表这样的对象是 易变的 . 这意味着可以通过指向对象的任何内容来更改对象的内容。例如, x = []; y = x; x.append(10); print y 将打印 [10] . 已创建空列表。“x”和“y”都指向同一列表。这个 追加 方法改变(更新)列表对象(如向数据库添加记录),结果对“x”和“y”都可见(就像数据库更新对该数据库的每个连接都可见一样)。

    希望你能澄清这个问题。

        7
  •  55
  •   Community CDub    8 年前

    技术上, python总是使用pass-by引用值 . 我要重复一遍 my other answer 支持我的声明。

    python总是使用传递引用值。没有例外。任何变量赋值都意味着复制引用值。也不例外。任何变量都是绑定到引用值的名称。总是。

    可以将引用值视为目标对象的地址。地址在使用时自动取消引用。这样,使用引用值时,您似乎直接使用目标对象。但是在两者之间总是有一个参照物,要跳到目标上还要多走一步。

    下面的示例证明了Python使用了按引用传递:

    Illustrated example of passing the argument

    如果参数是按值传递的,则外部 lst 无法修改。绿色是目标对象(黑色是存储在其中的值,红色是对象类型),黄色是内存,其中的引用值——绘制为箭头。蓝色实心箭头是传递给函数的参考值(通过蓝色虚线箭头路径)。难看的深黄色是内部词典。(实际上也可以画成绿色椭圆。颜色和形状只表示它是内部的。)

    你可以使用 id() 内置函数,用于了解引用值是什么(即目标对象的地址)。

    在编译语言中,变量是能够捕获类型值的内存空间。在Python中,变量是绑定到保存目标对象引用值的引用变量的名称(在内部捕获为字符串)。变量的名称是内部字典中的键,该字典项的值部分存储对目标的引用值。

    引用值隐藏在python中。没有任何用于存储引用值的显式用户类型。但是,可以使用一个列表元素(或任何其他合适容器类型中的元素)作为引用变量,因为所有容器都将元素存储为对目标对象的引用。换句话说,元素实际上不包含在容器中——只有对元素的引用才是。

        8
  •  41
  •   AmanicA    13 年前

    我通常使用的一个简单技巧是将它包装在一个列表中:

    def Change(self, var):
        var[0] = 'Changed'
    
    variable = ['Original']
    self.Change(variable)      
    print variable[0]
    

    (是的,我知道这很不方便,但有时做起来很简单。)

        9
  •  35
  •   KobeJohn    9 年前

    (编辑-布莱尔更新了他广受欢迎的答案,使其准确无误)

    我认为重要的是要注意到,目前获得最多选票的职位(布莱尔·康拉德),虽然其结果是正确的,但具有误导性,并且根据其定义是不正确的。虽然有许多语言(如C)允许用户通过引用或传递值,但python不是其中之一。

    大卫·库尔纳波的回答指向了真实的答案,并解释了为什么布莱尔·康拉德的文章中的行为似乎是正确的,而定义却不正确。

    如果python是传递值,那么所有语言都是传递值,因为必须发送一些数据(无论是“值”还是“引用”)。然而,这并不意味着在C程序员会想到的意义上,python是传递值。

    如果你想这样做,布莱尔·康拉德的回答很好。但是如果你想知道为什么python既不是传递值,也不是传递引用,那么请阅读大卫·库纳波的答案。

        10
  •  32
  •   Lutz Prechelt    8 年前

    python中没有变量

    理解参数传递的关键是停止思考“变量”。在python中有名称和对象,它们在一起 看起来像变量,但总是区分这三个变量是有用的。

    1. python有名称和对象。
    2. 赋值将名称绑定到对象。
    3. 将参数传递到函数中也会将名称(函数的参数名称)绑定到对象。

    这就是一切。易变性与这个问题无关。

    例子:

    a = 1
    

    这将绑定名称 a 一个整数类型的对象,该对象保存值1。

    b = x
    

    这将绑定名称 b 到相同的对象 x 当前绑定到。 之后,名字 与名字无关 X 再。

    见章节 3.1 4.2 在python 3语言参考中。


    所以在问题中所示的代码中, self.Change(self.variable) 绑定名称 var (在功能范围内 Change )到保存值的对象 'Original' 以及任务 var = 'Changed' (在职能范围内 变化 )再次将相同的名称赋给另一个对象(恰好也包含一个字符串,但可能完全是另一个对象)。

        11
  •  23
  •   Alex L    13 年前

    你得到了一些很好的答案。

    x = [ 2, 4, 4, 5, 5 ]
    print x  # 2, 4, 4, 5, 5
    
    def go( li ) :
      li = [ 5, 6, 7, 8 ]  # re-assigning what li POINTS TO, does not
      # change the value of the ORIGINAL variable x
    
    go( x ) 
    print x  # 2, 4, 4, 5, 5  [ STILL! ]
    
    
    raw_input( 'press any key to continue' )
    
        12
  •  17
  •   Mike Mazur    16 年前

    在这种情况下,变量标题为 var 在该方法中 Change 分配了对的引用 self.variable ,然后立即将字符串赋给 var . 它不再指向 自变量 . 下面的代码段显示了如果修改 var 自变量 ,在这种情况下,列表:

    >>> class PassByReference:
    ...     def __init__(self):
    ...         self.variable = ['Original']
    ...         self.change(self.variable)
    ...         print self.variable
    ...         
    ...     def change(self, var):
    ...         var.append('Changed')
    ... 
    >>> q = PassByReference()
    ['Original', 'Changed']
    >>> 
    

    我相信其他人可以进一步澄清这一点。

        13
  •  15
  •   ajknzhol    10 年前

    Python E-Sy-By赋值方案与C++AES的参考参数选项完全相同,但实际上与C语言(和其他语言)的参数传递模型非常类似:

    • 不可变的参数被有效地传递给__ 按价值 .
    • 可变参数被有效传递 用指针 . 字典也通过对象引用传递,这类似于C 将数组作为指针传递。可以在函数中就地更改可变对象, 很像C数组。
        14
  •  15
  •   John Smith jjcaicedo    8 年前

    正如您所说,您需要有一个可变的对象,但让我建议您检查全局变量,因为它们可以帮助您甚至解决此类问题!

    http://docs.python.org/3/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python

    例子:

    >>> def x(y):
    ...     global z
    ...     z = y
    ...
    
    >>> x
    <function x at 0x00000000020E1730>
    >>> y
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    NameError: name 'y' is not defined
    >>> z
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    NameError: name 'z' is not defined
    
    >>> x(2)
    >>> x
    <function x at 0x00000000020E1730>
    >>> y
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    NameError: name 'y' is not defined
    >>> z
    2
    
        15
  •  12
  •   Joop    10 年前

    这里有很多关于答案的见解,但我认为这里没有明确提到另外一点。从python文档引用 https://docs.python.org/2/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python

    “在python中,只在函数内部引用的变量是隐式全局的。如果在函数体的任何位置为变量分配了一个新值,则该变量假定为局部变量。如果在函数中为变量分配了新值,则该变量是隐式局部变量,您需要将其显式声明为__global_。 虽然一开始有点令人惊讶,但片刻的思考解释了这一点。一方面,为指定的变量要求全局性可以提供一个防止意外副作用的条。另一方面,如果所有全局引用都需要全局引用,则始终使用全局引用。您必须将对内置函数或导入模块组件的每个引用声明为全局引用。这种混乱将破坏全球声明识别副作用的有效性。”

    即使将可变对象传递给函数,这仍然适用。对我来说,清楚地解释了分配给对象和在函数中操作对象之间行为差异的原因。

    def test(l):
        print "Received", l , id(l)
        l = [0, 0, 0]
        print "Changed to", l, id(l)  # New local object created, breaking link to global l
    
    l= [1,2,3]
    print "Original", l, id(l)
    test(l)
    print "After", l, id(l)
    

    给予:

    Original [1, 2, 3] 4454645632
    Received [1, 2, 3] 4454645632
    Changed to [0, 0, 0] 4474591928
    After [1, 2, 3] 4454645632
    

    因此,对未声明为全局的全局变量的赋值将创建一个新的本地对象,并断开与原始对象的链接。

        16
  •  9
  •   Community CDub    8 年前

    这是对这个概念的简单(我希望)解释 pass by object 在Python中使用。
    每当您将对象传递给函数时,对象本身就被传递(Python中的对象实际上是您在其他编程语言中调用的值),而不是对该对象的引用。换句话说,当你打电话时:

    def change_me(list):
       list = [1, 2, 3]
    
    my_list = [0, 1]
    change_me(my_list)
    

    正在传递实际对象-[0,1](在其他编程语言中称为值)。所以实际上函数 change_me 将尝试执行以下操作:

    [0, 1] = [1, 2, 3]
    

    这显然不会改变传递给函数的对象。如果函数如下所示:

    def change_me(list):
       list.append(2)
    

    然后调用将导致:

    [0, 1].append(2)
    

    这显然会改变物体。 This answer 解释得很好。

        17
  •  8
  •   John Smith jjcaicedo    8 年前

    除了所有关于这些东西如何在Python中工作的很好的解释之外,我没有看到一个简单的关于这个问题的建议。就像创建对象和实例一样,处理实例变量和更改实例变量的方法如下:

    class PassByReference:
        def __init__(self):
            self.variable = 'Original'
            self.Change()
            print self.variable
    
        def Change(self):
            self.variable = 'Changed'
    

    在实例方法中,通常指 self 访问实例属性。在中设置实例属性是正常的 __init__ 并在实例方法中读取或更改它们。这也是你通过的原因 自己 第一个论点是 def Change .

    另一种解决方案是创建这样的静态方法:

    class PassByReference:
        def __init__(self):
            self.variable = 'Original'
            self.variable = PassByReference.Change(self.variable)
            print self.variable
    
        @staticmethod
        def Change(var):
            var = 'Changed'
            return var
    
        18
  •  6
  •   itmuckel    9 年前

    有一个小技巧可以通过引用传递一个对象,即使语言不能使之成为可能。它也在Java中工作,它是带有一个项目的列表。;-)

    class PassByReference:
        def __init__(self, name):
            self.name = name
    
    def changeRef(ref):
        ref[0] = PassByReference('Michael')
    
    obj = PassByReference('Peter')
    print obj.name
    
    p = [obj] # A pointer to obj! ;-)
    changeRef(p)
    
    print p[0].name # p->name
    

    这是一个丑陋的黑客,但它起作用。-P

        19
  •  4
  •   John Smith jjcaicedo    8 年前

    我使用以下方法快速地将两个Fortran代码转换为python。是的,当最初的问题被提出时,它不是通过引用传递的,但在某些情况下是一个简单的工作。

    a=0
    b=0
    c=0
    def myfunc(a,b,c):
        a=1
        b=2
        c=3
        return a,b,c
    
    a,b,c = myfunc(a,b,c)
    print a,b,c
    
        20
  •  3
  •   textshell    8 年前

    虽然pass-by引用不适合python,而且很少使用,但有一些解决方法可以实际工作,以使对象当前分配给局部变量,甚至从被调用函数内部重新分配局部变量。

    其基本思想是拥有一个可以进行访问的函数,并且可以作为对象传递到其他函数或存储在类中。

    一种方法是使用 global (对于全局变量)或 nonlocal (对于函数中的局部变量)在包装函数中。

    def change(wrapper):
        wrapper(7)
    
    x = 5
    def setter(val):
        global x
        x = val
    print(x)
    

    同样的想法也适用于阅读和 del 使变量保持不变。

    对于只是阅读,甚至还有一种更短的方法来使用 lambda: x 它返回一个调用,当调用时返回当前值x。这有点像在遥远的过去语言中使用的“按名称调用”。

    传递3个包装器来访问变量有点难,因此可以将这些包装器包装到具有代理属性的类中:

    class ByRef:
        def __init__(self, r, w, d):
            self._read = r
            self._write = w
            self._delete = d
        def set(self, val):
            self._write(val)
        def get(self):
            return self._read()
        def remove(self):
            self._delete()
        wrapped = property(get, set, remove)
    
    # left as an exercise for the reader: define set, get, remove as local functions using global / nonlocal
    r = ByRef(get, set, remove)
    r.wrapped = 15
    

    pythons的“反射”支持使获得一个对象成为可能,该对象能够在给定范围内重新分配名称/变量,而无需在该范围内显式定义函数:

    class ByRef:
        def __init__(self, locs, name):
            self._locs = locs
            self._name = name
        def set(self, val):
            self._locs[self._name] = val
        def get(self):
            return self._locs[self._name]
        def remove(self):
            del self._locs[self._name]
        wrapped = property(get, set, remove)
    
    def change(x):
        x.wrapped = 7
    
    def test_me():
        x = 6
        print(x)
        change(ByRef(locals(), "x"))
        print(x)
    

    这里 ByRef 类包装字典访问。所以属性访问 wrapped 在传递的字典中转换为项访问。通过传递内置的结果 locals 局部变量的名称,最终访问局部变量。从3.5开始的python文档建议更改字典可能不起作用,但它似乎对我有用。

        21
  •  3
  •   mARK bLOORE    8 年前

    考虑到python处理值和引用它们的方式,引用任意实例属性的唯一方法是按名称:

    class PassByReferenceIsh:
        def __init__(self):
            self.variable = 'Original'
            self.change('variable')
            print self.variable
    
        def change(self, var):
            self.__dict__[var] = 'Changed'
    

    当然,在真正的代码中,您可以在dict查找中添加错误检查。

        22
  •  2
  •   Jesse Hogan    7 年前

    由于您的示例恰好是面向对象的,因此您可以进行以下更改以获得类似的结果:

    class PassByReference:
        def __init__(self):
            self.variable = 'Original'
            self.change('variable')
            print(self.variable)
    
        def change(self, var):
            setattr(self, var, 'Changed')
    
    # o.variable will equal 'Changed'
    o = PassByReference()
    assert o.variable == 'Changed'
    
        23
  •  2
  •   Alok Garg    6 年前

    Python中的PASS引用与C++/Java中的按引用传递概念有很大的不同。

    • 爪哇: 所有内容都是通过引用传递的,因此调用函数中参数所做的所有更改对调用方都是可见的。
    • C++: 允许传递引用或传递值。如果参数是通过引用传递的,则可以根据参数是否作为常量传递来修改它。但是,无论常量与否,参数维护对对象的引用,并且不能将引用分配给所调用函数中的其他对象。
    • 蟒蛇: python是_156;pass-by-object-reference_,其中常说:_156;object-references是按值传递的。 1 .调用者和函数都引用同一个对象,但函数中的参数是一个新变量,它只在调用者中保存对象的副本。像C++一样,一个参数可以在函数中被修改或不被修改,这取决于传递的对象的类型。在被调用函数中不能修改不可变的对象类型,而可变的对象可以更新或重新初始化。更新或重新分配/重新初始化可变变量之间的一个重要区别是,更新后的值会反映回被调用的函数中,而重新初始化的值则不会。将新对象分配给可变变量的范围是Python中函数的局部范围。@blair conrad提供的例子很好地理解了这一点。
        24
  •  0
  •   sergzach    7 年前

    你只能用 空类 作为实例存储引用对象,因为内部对象属性存储在实例字典中。请参见示例。

    class RefsObj(object):
        "A class which helps to create references to variables."
        pass
    
    ...
    
    # an example of usage
    def change_ref_var(ref_obj):
        ref_obj.val = 24
    
    ref_obj = RefsObj()
    ref_obj.val = 1
    print(ref_obj.val) # or print ref_obj.val for python2
    change_ref_var(ref_obj)
    print(ref_obj.val)