如何通过引用传递变量?

问:

参数是按引用传递还是按值传递?如何通过引用传递,以便下面的代码输出 ‘Changed’ 而不是 ‘Original’?

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

    def change(self, var):
        var = 'Changed'

答1:

打造属于自己的副业,开启自由职业之旅,从huntsbot.com开始!

参数是 passed by assignment。这背后的理由是双重的:

传入的参数实际上是对对象的引用(但引用是按值传递的)有些数据类型是可变的,但有些不是

所以:

如果你将一个可变对象传递给一个方法,该方法会获得对同一个对象的引用,你可以随心所欲地改变它,但是如果你在方法中重新绑定引用,外部作用域将一无所知,并且之后大功告成,外部引用仍将指向原始对象。

如果将不可变对象传递给方法,仍然无法重新绑定外部引用,甚至无法改变对象。

为了更清楚,让我们举一些例子。

List - 可变类型

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

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 的引用,而不是它的副本,因此我们可以使用 mutating 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 参数是按值传递的,因此为它分配一个新列表对方法外部的代码没有任何影响。 the_list 是 outer_list 引用的副本,我们让 the_list 指向一个新列表,但无法更改 outer_list 指向的位置。

String - 不可变类型

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

现在,让我们尝试更改参考

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 参数是按值传递的,因此为它分配一个新字符串对方法外部的代码没有任何影响。 the_string 是 outer_string 引用的副本,我们让 the_string 指向一个新字符串,但无法更改 outer_string 指向的位置。

我希望这能澄清一点。

编辑:有人指出,这并没有回答@David 最初提出的问题,“我可以做些什么来通过实际引用传递变量吗?”。让我们继续努力。

我们如何解决这个问题?

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

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])

虽然这看起来有点麻烦。

然后在 C 中也是如此,当您通过“按引用”传递时,您实际上是按值传递引用...定义“按引用”:P

我不确定我是否理解你的条款。我已经退出 C 游戏有一段时间了,但是当我参与其中时,没有“按引用传递”——你可以传递东西,它总是按值传递,所以参数列表中的任何内容被复制了。但有时事情是一个指针,它可以跟随到一块内存(原始,数组,结构,等等),但是你不能改变从外部范围复制的指针 - 当你完成函数时,原来的指针仍然指向同一个地址。 C++ 引入了引用,其行为不同。

@Zac Bowling我真的不明白你所说的在实际意义上与这个答案有什么关系。如果 Python 新手想知道通过 ref/val 传递,那么从这个答案中得出的结论是: 1- 您可以使用函数接收的引用作为其参数,来修改变量的“外部”值,只要因为您没有重新分配参数来引用新对象。 2-分配给不可变类型将始终创建一个新对象,这会破坏您对外部变量的引用。

@CamJackson,你需要一个更好的例子——数字在 Python 中也是不可变的对象。此外,如果在等号左侧没有下标的 any 分配将重新分配名称到一个新对象,无论它是否不可变,这不是真的吗? def Foo(alist): alist = [1,2,3] 将不会从调用者的角度修改列表的内容。

-1。显示的代码很好,关于如何的解释是完全错误的。有关原因的正确解释,请参阅 DavidCournapeau 或 DarenThomas 的答案。

答2:

huntsbot.com洞察每一个产品背后的需求与收益,从而捕获灵感

问题来自对 Python 中的变量的误解。如果您习惯于大多数传统语言,那么您对以下顺序发生的事情有一个心智模型:

a = 1
a = 2

您认为 a 是存储值 1 的内存位置,然后被更新以存储值 2。这不是 Python 中的工作方式。相反,a 开始是对具有值 1 的对象的引用,然后被重新分配为对具有值 2 的对象的引用。即使 a 不再引用第一个对象,这两个对象也可能继续共存;事实上,它们可能被程序中任意数量的其他引用共享。

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

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

def Change(self, var):
    var = 'Changed'

self.variable 是对字符串对象 ‘Original’ 的引用。当您调用 Change 时,您会创建对该对象的第二个引用 var。在函数内部,您将引用 var 重新分配给不同的字符串对象 ‘Changed’,但引用 self.variable 是独立的并且不会更改。

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

def __init__(self):         
    self.variable = ['Original']
    self.Change(self.variable)

def Change(self, var):
    var[0] = 'Changed'

好简洁的解释。您的段落“当您调用函数时......”是我听到的“Python 函数参数是引用,按值传递”这个相当神秘的短语的最佳解释之一。我认为,如果您仅理解该段落,那么其他所有内容都是有意义的,并且从那里得出合乎逻辑的结论。然后,您只需要注意何时创建新对象以及何时修改现有对象。

但是如何重新分配参考?我以为您不能更改“var”的地址,但您的字符串“已更改”现在将存储在“var”内存地址中。您的描述使“已更改”和“原始”似乎属于内存中的不同位置,而您只需将“var”切换到不同的地址。那是对的吗?

@Glassjawed,我想你明白了。 “Changed”和“Original”是不同内存地址的两个不同字符串对象,“var”从指向一个变为指向另一个。

@TonySuffolk66 id 给出了引用对象的标识,而不是引用本身。

@MinhTran 用最简单的术语来说,引用是“引用”对象的东西。它的物理表示很可能是一个指针,但这只是一个实现细节。这实际上是一个抽象的概念。

答3:

huntsbot.com – 程序员副业首选,一站式外包任务、远程工作、创意产品分享订阅平台。

https://i.stack.imgur.com/FdaCu.png

可爱的,可以很容易地发现中间任务的细微差异,对于不经意的旁观者来说并不明显。 +1

A 是否可变并不重要。如果你给 B 分配不同的东西,A 不会改变。如果一个对象是可变的,你可以改变它,当然。但这与直接分配给名称无关。

@Martijn你是对的。我删除了提到可变性的答案部分。我不认为它现在可以变得更简单了。

感谢更新,好多了!让大多数人感到困惑的是订阅的分配。例如B[0] = 2,与直接分配,B = 2。

“A 分配给 B。”这不是模棱两可吗?我认为在普通英语中可以表示 A=B 或 B=A。

答4:

huntsbot.com – 程序员副业首选,一站式外包任务、远程工作、创意产品分享订阅平台。

它既不是按值传递也不是按引用传递 - 它是按对象调用。看这个,Fredrik Lundh:

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

这是一个重要的报价:

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

在您的示例中,当调用 Change 方法时,会为其创建一个 namespace; var 成为该名称空间内字符串对象 ‘Original’ 的名称。然后该对象在两个命名空间中有一个名称。接下来,var = ‘Changed’ 将 var 绑定到一个新的字符串对象,因此该方法的命名空间忘记了 ‘Original’。最后,这个命名空间被遗忘了,字符串 ‘Changed’ 也被遗忘了。

一个优秀的自由职业者,应该有对需求敏感和精准需求捕获的能力,而huntsbot.com提供了这个机会

我觉得很难买到。对我来说就像 Java 一样,参数是指向内存中对象的指针,这些指针是通过堆栈或寄存器传递的。

这不像java。不一样的情况之一是不可变对象。想想平凡的函数 lambda x: x。将此应用于 x = [1, 2, 3] 和 x = (1, 2, 3)。在第一种情况下,返回的值将是输入的副本,在第二种情况下相同。

不,它与 Java 的对象语义完全一样。我不确定“在第一种情况下,返回的值将是输入的副本,在第二种情况下相同”是什么意思。但这种说法似乎显然是不正确的。

它与 Java 中的完全相同。对象引用按值传递。任何有不同想法的人都应该为可以交换两个引用的 swap 函数附加 Python 代码,如下所示:a = [42] ; b = 'Hello'; swap(a, b) # Now a is 'Hello', b is [42]

在 Java 中传递对象时,它与 Java 完全相同。但是,Java 也有原语,它们是通过复制原语的值来传递的。因此在这种情况下它们是不同的。

答5:

保持自己快人一步,享受全网独家提供的一站式外包任务、远程工作、创意产品订阅服务–huntsbot.com

想想通过赋值而不是通过引用/按值传递的东西。这样一来,只要您了解正常分配期间发生的事情,就会很清楚发生了什么。

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

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

由于无法修改不可变类型,它们似乎是按值传递的——将 int 传递给函数意味着将 int 分配给函数的参数。您只能重新分配它,但它不会更改原始变量值。

乍一看,这个答案似乎回避了最初的问题。在第二次阅读之后,我意识到这让事情变得非常清楚。可以在此处找到对“名称分配”概念的良好跟进:Code Like a Pythonista: Idiomatic Python

答6:

保持自己快人一步,享受全网独家提供的一站式外包任务、远程工作、创意产品订阅服务–huntsbot.com

Python中没有变量

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

Python 有名称和对象。赋值将名称绑定到对象。将参数传递给函数还会将名称(函数的参数名称)绑定到对象。

这就是它的全部。可变性与这个问题无关。

例子:

a = 1

这将名称 a 绑定到具有值 1 的整数类型的对象。

b = x

这会将名称 b 绑定到名称 x 当前绑定到的同一对象。之后,名称 b 与名称 x 不再有任何关系。

请参阅 Python 3 语言参考中的 3.1 和 4.2 部分。

如何阅读问题中的示例

在问题所示的代码中,语句 self.Change(self.variable) 将名称 var(在函数 Change 的范围内)绑定到包含值 ‘Original’ 和赋值 var = ‘Changed’(在函数 Change) 再次将相同的名称分配给某个其他对象(它恰好也包含一个字符串,但可能完全是其他东西)。

如何通过引用传递

因此,如果您要更改的是可变对象,则没有问题,因为所有内容都通过引用有效地传递。

如果它是一个 immutable 对象(例如,布尔值、数字、字符串),则可以将其包装在一个可变对象中。 快速而简单的解决方案是单元素列表 (代替 self.variable,传递 [self.variable] 并在函数中修改 var[0])。更多的 pythonic 方法是引入一个微不足道的单属性类。该函数接收该类的一个实例并操作该属性。

“Python 没有变量”是一个愚蠢而令人困惑的口号,我真的希望人们不要再这么说了...... :( 这个答案的其余部分很好!

这可能令人震惊,但并不愚蠢。而且我认为这也不令人困惑:它希望能够为即将到来的解释打开接收者的思想,并使她处于一种有用的“我想知道他们有什么而不是变量”的态度。 (是的,您的里程可能会有所不同。)

你还会说 Javascript 没有变量吗?它们的工作方式与 Python 相同。此外,Java、Ruby、PHP ……我认为更好的教学技术是“Python 的变量工作方式与 C 的不同”。

是的,Java 有变量。 Python 和 JavaScript、Ruby、PHP 等也是如此。您不会在 Java 中说 int 声明了一个变量,但 Integer 没有。他们都声明了变量。 Integer 变量是一个对象,int 变量是一个原语。例如,您通过显示 a = 1; b = a; a++ # doesn't modify b 演示了变量的工作方式。在 Python 中也是如此(使用 += 1,因为 Python 中没有 ++)!

“变量”的概念很复杂,而且通常很模糊:变量是值的容器,由名称标识。在 Python 中,值是对象,容器是对象(看到问题了吗?),名称实际上是独立的事物。我相信以这种方式准确理解变量要困难得多。名称和对象的解释看起来更难,但实际上更简单。

答7:

huntsbot.com汇聚了国内外优秀的初创产品创意,可按收入、分类等筛选,希望这些产品与实践经验能给您带来灵感。

Effbot(又名 Fredrik Lundh)将 Python 的变量传递风格描述为按对象调用:http://effbot.org/zone/call-by-object.htm

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

当您进行诸如 x = 1000 之类的赋值时,将创建一个字典条目,将当前命名空间中的字符串“x”映射到指向包含一千的整数对象的指针。

当您使用 x = 2000 更新“x”时,将创建一个新的整数对象,并且更新字典以指向新对象。旧的 1000 个对象没有改变(并且可能存在也可能不存在,具体取决于是否有其他对象引用该对象)。

当您执行新的赋值(例如 y = x)时,会创建一个新的字典条目“y”,它指向与“x”条目相同的对象。

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

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

希望能为您澄清问题。

我非常感谢从开发人员那里了解这一点。正如 pepr 的回答所暗示的那样,id() 函数是否返回指针的(对象引用的)值?

@HonestAbe 是的,在 CPython 中 id() 返回地址。但在 PyPy 和 Jython 等其他 python 中,id() 只是一个唯一的对象标识符。

答8:

一个优秀的自由职业者,应该有对需求敏感和精准需求捕获的能力,而huntsbot.com提供了这个机会

从技术上讲,Python 总是使用传递引用值。我将重复 my other answer 以支持我的陈述。

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

您可以将引用值视为目标对象的地址。该地址在使用时会自动取消引用。这样,使用参考值,您似乎直接使用目标对象。但中间总有一个参考,多一步跳转到目标。

这是证明 Python 使用按引用传递的示例:

https://i.stack.imgur.com/uzXcP.png

如果参数是按值传递的,则无法修改外部 lst。绿色是目标对象(黑色是里面存储的值,红色是对象类型),黄色是里面有引用值的内存——如箭头所示。蓝色实心箭头是传递给函数的参考值(通过蓝色虚线箭头路径)。丑陋的深黄色是内部字典。 (它实际上也可以画成一个绿色的椭圆。颜色和形状只是说它是内部的。)

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

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

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

实际上这是通过参考值证实了它的通过。尽管这个例子不好,但这个答案+1。

发明新的术语(例如“按引用值传递”或“按对象调用”没有帮助)。 “由(值|参考|名称)调用”是标准术语。 “参考”是一个标准术语。按值传递引用准确地描述了 Python、Java 和许多其他语言的行为,使用标准术语。

@cayhorstmann:问题在于 Python 变量的术语含义与其他语言不同。这样,引用调用在这里就不太合适了。另外,您如何准确定义术语参考?非正式地,Python 方式可以很容易地描述为传递对象的地址。但它不适合 Python 的潜在分布式实现。

我喜欢这个答案,但你可能会考虑这个例子是真的帮助还是伤害了流程。此外,如果您将“参考值”替换为“对象参考”,您将使用我们可以认为是“官方”的术语,如下所示:Defining Functions

该引用的末尾有一个脚注,内容如下:“实际上,按对象引用调用会是一个更好的描述,因为如果传递了一个可变对象,调用者将看到被调用者对其所做的任何更改...” 我同意您的观点,即混淆是由于试图适应与其他语言建立的术语而引起的。抛开语义不谈,需要了解的有:字典/命名空间、name binding operations 以及名称→指针→对象的关系(如您所知)。

答9:

huntsbot.com – 高效赚钱,自由工作

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

def Change(self, var):
    var[0] = 'Changed'

variable = ['Original']
self.Change(variable)      
print variable[0]

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

+1 用于少量文本,为 Python 没有传递引用的问题提供了基本的解决方法。 (作为适合此处以及此页面上任何地方的后续评论/问题:我不清楚为什么 python 不能像 C# 那样提供“ref”关键字,它只是将调用者的参数包装在一个列表中,如this,并将对函数中参数的引用视为列表的第 0 个元素。)

好的。要通过 ref,请用 [ ] 包裹起来。

答10:

一个优秀的自由职业者,应该有对需求敏感和精准需求捕获的能力,而huntsbot.com提供了这个机会

(编辑 - 布莱尔更新了他广受欢迎的答案,现在它是准确的)

我认为重要的是要注意当前投票最多的帖子(由布莱尔康拉德撰写),虽然就其结果而言是正确的,但它具有误导性,并且根据其定义是不正确的。虽然有许多语言(如 C)允许用户通过引用传递或按值传递,但 Python 不是其中之一。

David Cournapeau 的回答指向了真正的答案,并解释了为什么 Blair Conrad 的帖子中的行为似乎是正确的,而定义却不是。

在 Python 是按值传递的范围内,所有语言都是按值传递的,因为必须发送一些数据(无论是“值”还是“引用”)。但是,这并不意味着 Python 在 C 程序员所认为的意义上是按值传递的。

如果你想要这种行为,布莱尔康拉德的回答很好。但是,如果您想了解 Python 既不是按值传递也不是按引用传递的具体细节,请阅读 David Cournapeau 的回答。

并非所有语言都是按值调用的。在 C++ 或 Pascal 中(当然还有许多我不知道的其他语言),您可以通过引用调用。例如,在 C++ 中,void swap(int& x, int& y) { int temp = x; x = y; y = temp; } 将交换传递给它的变量。在 Pascal 中,您使用 var 而不是 &。

我以为我很久以前就回复了这个,但我没有看到它。为了完整性 - cayhorstmann 误解了我的回答。我并不是说按照大多数人首先学习的有关 C/C++ 的术语来说,一切都是按价值调用的。只是传递了一些值(值、名称、指针等),并且 Blair 原始答案中使用的术语不准确。

答11:

与HuntsBot一起,探索全球自由职业机会–huntsbot.com

你在这里得到了一些非常好的答案。

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' )

是的,但是如果你这样做 x = [ 2, 4, 4, 5, 5], y = x, X[0] = 1 , print x # [1, 4 ,4, 5, 5] print y # [1 , 4, 4, 5, 5]

X[0] 还是 x[0] ?不明白

原文链接:https://www.huntsbot.com/qa/DkM6/how-do-i-pass-a-variable-by-reference?lang=zh_CN&from=csdn

一个优秀的自由职业者,应该有对需求敏感和精准需求捕获的能力,而huntsbot.com提供了这个机会

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值