如何通过引用传递变量?

Dav*_*kes 2480 python reference parameter-passing pass-by-reference

Python文档似乎不清楚参数是通过引用还是值传递,以下代码生成未更改的值'Original'

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

    def change(self, var):
        var = 'Changed'
Run Code Online (Sandbox Code Playgroud)

有什么我可以通过实际参考传递变量吗?

Bla*_*rad 2668

参数通过赋值传递.这背后的理由是双重的:

  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)
Run Code Online (Sandbox Code Playgroud)

输出:

before, outer_list = ['one', 'two', 'three']
got ['one', 'two', 'three']
changed to ['one', 'two', 'three', 'four']
after, outer_list = ['one', 'two', 'three', 'four']
Run Code Online (Sandbox Code Playgroud)

由于传入的参数是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)
Run Code Online (Sandbox Code Playgroud)

输出:

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']
Run Code Online (Sandbox Code Playgroud)

由于the_list参数是按值传递的,因此为其分配新列表不会影响方法外部的代码.这the_listouter_list引用的副本,我们the_list指向了一个新列表,但没有办法改变outer_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)
Run Code Online (Sandbox Code Playgroud)

输出:

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
Run Code Online (Sandbox Code Playgroud)

同样,由于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)
Run Code Online (Sandbox Code Playgroud)

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

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])
Run Code Online (Sandbox Code Playgroud)

虽然这看起来有点麻烦.

  • 然后同样在C中,当你通过"引用"时,你实际上是通过_by value_引用...定义"通过引用":P (157认同)
  • 我不确定我理解你的条款.我已经离开了C游戏一段时间了,但当我进入它时,没有"通过参考传递" - 你可以传递东西,它总是按值传递,所以参数列表中的任何内容被复制了.但有时候东西是一个指针,它可以跟随内存(原始,数组,结构,等等),但你无法改变从外部作用域复制的指针 - 当你完成了这个功能,原始指针仍指向同一地址.C++引入了引用,其行为不同. (96认同)
  • -1.显示的代码很好,关于如何完全错误的解释.请参阅DavidCournapeau或DarenThomas的答案,以获得有关原因的正确解释. (56认同)
  • @Zac保龄球我真的不明白你所说的话在实际意义上与这个答案有关.如果Python新手想知道通过ref/val传递,那么这个答案的结果是:**1 - **你*可以*使用函数接收的引用作为其参数,来修改'outside'值变量,只要您不重新分配参数以引用新对象.**2 - **分配给一个不可变类型将*总是*创建一个新对象,它打破了你对外部变量的引用. (29认同)
  • @CamJackson,你需要一个更好的例子 - 数字也是Python中的不可变对象.此外,如果没有在equals左侧下标的*any*赋值将重新分配给新对象,无论它是否是不可变的,这不是真的吗?`def Foo(alist):alist = [1,2,3]`****不会**从调用者的角度修改列表的内容. (9认同)
  • 哦,那很难过."它通过值传递,但值是参考".叹.嗯,*一切*是一个价值.当值是引用时,通过引用传递.Python中的差异很小,因为Pythons变量不像C的变量,但调用它传递值肯定是不正确的. (7认同)
  • @Lennart Regebro,只是因为该值是对某事物的引用并不能使其通过引用传递.在C++中,您实际上可以通过引用传递,当您执行此操作时,如果重新分配引用,则实际将修改您传递的参数.在Python中,如果您尝试重新分配参数,您将只更改函数名称空间中名称的映射,这是根本不同的.尽管如此,通过引用传递的整个想法现在已经搞砸了,因为每个人都在使用它来描述不同的东西. (7认同)
  • @BlairConrad,在C中传递"by reference"只是一个公认的约定,你将指针传递给某些东西而不是某些东西的值.在这方面,它的行为与Python完全相同:您可以更新指针指向的值,但更新指针本身在函数外部没有任何影响,因为指针是按值传递的. (6认同)
  • 提出可变性只会增加混乱.它实际上没有任何问题.你首先说明它是正确的,因为你可以改变你的范围内的东西的引用(比如指针指针). (4认同)
  • @Zac Bowling Mutability是完全相关的.如果字符串是可变的,那么第一个字符串示例将具有不同的输出.重要的是要知道设置传入字符串的值将创建一个新的字符串对象,而不是修改传递的字符串对象.可变性是防止字符串参数表现相同的原因,在这种情况下是整数参数. (4认同)
  • _"...传入的参数......"_是不正确的术语使用.[parameter](http://docs.python.org/3/glossary.html#term-parameter)是函数(或方法)定义中的命名实体,它指定一个参数(或在某些情况下,参数)功能可以接受.[argument](http://docs.python.org/3/glossary.html#term-argument)是在调用函数时传递给函数(或方法)的值. (4认同)
  • @Cam Jackson实际上并不相关.通过引用传递意味着能够更改调用引用正在使用的指针.谈论更改数据时,指针的目标会增加混乱,只会增加实现细节. (3认同)
  • 我投了反对票,因为为 string 和 list 给出的代码(在函数体中进行赋值的地方)基本相同并且具有相同的行为。列表是可变的,而字符串则不是。你的回答并没有为我清除任何东西。这让我意识到我完全不了解 Python。 (3认同)
  • @HappyAhmad 它与通过引用调用绝对*不同*不同。如果Python支持引用调用,你可以执行类似“def foo(&var): var = 2”然后“x = 0;”的操作。y = 1;foo(x); foo(y)` 那么 `print(x, y)` 将打印 `2 2` (3认同)
  • @andrea,不是它根本不像C.Conrad是正确的,但术语引用/值在python中令人困惑.这就是为什么你应该真正使用另一个术语(请参阅我的链接到effbot获得一个很好的解释) (2认同)
  • @Synderesis:*"在C++中"......* - 嗯,是的,但现在我们谈论的是Python.Python正在传递引用.由于它是通过引用,这是合理的"通过引用传递".*"在Python中,如果您尝试重新分配参数"* - 是的,但这是一个完全不同的问题.这是因为Python没有指向像C/C++这样的内存位置的变量.它有对象,变量是这些对象的名称. (2认同)
  • @Lennart Regebro:我意识到这一点,但最后你还可以考虑使用值作为引用来传递Python.当然,这就是我最后添加最后一行的原因.将术语定义为不同语言的不同内容无论如何都是无用的,因为定义这些术语的重点在于明确你所说的内容. (2认同)
  • 因此,这个答案在事实上是不正确的,并且被不了解Python内部的新手称赞为"谢谢".这就是为什么它让我伤心. (2认同)
  • @Lennart Regebro"它是一个作为值传递的引用,类型是引用/指针,你必须取消引用才能访问它." 我不明白这是怎么回事.Java使用引用但是按值传递,并且行为与Python相同,因为它具有对象和变量是引用这些对象的名称.您之前所做的陈述"当值是参考时,通过引用传递." 是不正确的,因为如果它通过值,您可以重新分配引用.但是,在通过引用传递时,如果您尝试重新分配您实际正在修改它的变量. (2认同)
  • @Synderesis:再一次,然后:http://effbot.org/zone/call-by-object.htm参见Davids的回答.通过定义传值,那么一切都是值得通过的.对于它的措辞没有传递引用,因为传递引用的是当您传递的值是引用时,但您不必将该值视为函数内的引用.Java所谓的"按值传递,值是一个引用"是**正是传递引用的内容.**重点是Python**和Java**所做的***不是完全符合其他语言中使用的概念***. (2认同)
  • @Lennart Regebro"对于它的写法没有传递引用,因为传递引用的是你传递的值是一个引用"在C++中你可以通过引用真正传递.Java是值得传递的:http://stackoverflow.com/questions/40480/is-java-pass-by-reference (2认同)
  • @EthanFurman你错了。即使您阅读了 DavidCournapeau 的答案中的链接,即 [Call By Object](http://effbot.org/zone/call-by-object.htm),您也会看到所有表达式“call by object”, “通过共享调用”或“通过对象引用调用”,与使用地址访问对象具有相同的含义。当然,地址必须从名称空间确定。我无法接受“通过对象引用调用”与“通过引用调用”不同。 (2认同)
  • @ML_Pro,“按赋值传递”似乎是Python文档编写者用来描述“按值传递”的术语。对于用户来说,我认为传递作为引用(或句柄)的值(如 Java 或 C# 等语言中发生的情况)与 Python 所做的事情之间没有功能差异,而且我永远不会使用术语“通过赋值传递”;它已被编辑到答案中,我认为与文档保持一致。 (2认同)
  • @BlairConrad 因为这只是混淆了语义的实现。它不是按值传递,因为该值没有被复制。*值是对象*,Python不支持像指针这样的引用类型。CPython 使用指针的事实是无关紧要的,我可以在 Fortran 中编写一个 Python 解释器,仅使用内部的引用调用,而这不会使 Python 进行引用调用。是的,“通过分配”是一种Python主义,但在学术上,它被称为[通过共享调用](https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_sharing) (2认同)

Mar*_*som 647

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

a = 1
a = 2
Run Code Online (Sandbox Code Playgroud)

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

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

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

def Change(self, var):
    var = 'Changed'
Run Code Online (Sandbox Code Playgroud)

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'
Run Code Online (Sandbox Code Playgroud)

  • 简洁明了的解释.你的段落"当你调用一个函数......"是我听说过的最好的解释之一,即'Python函数参数是引用,通过值传递的相当神秘的短语'.我想如果你单独理解这一段,那么其他一切只是有意义,并从那里作为一个合乎逻辑的结论.然后,您只需要知道何时创建新对象以及何时修改现有对象. (97认同)
  • @Glassjawed,我想你已经明白了."Changed"和"Original"是不同内存地址处的两个不同字符串对象,"var"从指向一个指向另一个指向另一个. (8认同)
  • @TonySuffolk66 `id` 给出了所引用对象的标识,而不是引用本身。 (4认同)
  • 但是如何重新分配参考?我以为你不能改变'var'的地址,但你的字符串"Changed"现在将被存储在'var'内存地址中.您的描述使其看起来像"已更改"和"原始"属于内存中的不同位置而您只需将"var"切换到其他地址即可.那是对的吗? (3认同)
  • 函数调用不会创建新的引用 - 在函数内部和外部使用 id 函数来确认这一点。不同之处在于当您尝试在函数内更改对象时,对象会发生什么情况。 (2认同)
  • @MinhTran,用最简单的术语来说,引用是“引用”对象的东西。它的物理表示很可能是一个指针,但这只是一个实现细节。从本质上讲,这确实是一个抽象概念。 (2认同)

Zen*_*dix 308

我发现其他答案相当漫长而复杂,所以我创建了这个简单的图来解释Python处理变量和参数的方式. 在此输入图像描述

  • "A被分配给B." 那不是暧昧吗?我认为在普通英语中,这可能意味着"A = B"或"B = A". (9认同)
  • A是否可变是无关紧要的.如果您为B分配不同的内容,*A不会更改*.如果一个对象是可变的,你肯定可以改变它.但这与直接分配名称无关. (8认同)
  • 可爱,可以很容易地发现有一个中间分配的微妙差异,对于一个不经意的旁观者来说并不明显。+1 (4认同)
  • 感谢您的更新,更好!让大多数人感到困惑的是分配订阅; 例如`B [0] = 2`,与直接分配相比,`B = 2`. (3认同)

Dav*_*eau 232

它既不是按值传递,也不是按引用传递 - 它是逐个调用的.请见Fredrik Lundh:

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

这是一个重要的引用:

"......变量[名称] 不是对象;它们不能用其他变量表示或由对象引用."

在您的示例中,Change调用方法时- 为其创建名称空间 ; 并var成为该命名空间中字符串对象的名称'Original'.然后,该对象在两个名称空间中具有名称.接下来,var = 'Changed'绑定var到一个新的字符串对象,因此该方法的命名空间忘记了'Original'.最后,忘记了该命名空间,并将字符串'Changed'与它一起使用.

  • 不,它与*对象的Java语义完全相同.我不确定你的意思是"在第一种情况下,返回的值将是输入的副本,在第二种情况下是相同的." 但这种说法似乎显然是不正确的. (25认同)
  • 在Java中传递对象时,它与Java完全相同.但是,Java也有原语,它们通过复制原语的值来传递.因此它们在这种情况下不同. (23认同)
  • 它与Java完全相同.对象引用按值传递.任何以不同方式思考的人都应该附加一个可以交换两个引用的`swap`函数的Python代码,如:`a = [42]; b ='你好'; swap(a,b)#现在a是'Hello',b是[42]` (22认同)
  • 我觉得很难买.对我而言就像Java一样,参数是指向内存中对象的指针,这些指针通过堆栈或寄存器传递. (21认同)
  • 这不像java.它不相同的情况之一是不可变对象.想想lambda x:x的简单函数.将此应用于x = [1,2,3]和x =(1,2,3).在第一种情况下,返回的值将是输入的副本,并且在第二种情况下是相同的. (10认同)
  • 是否某个东西是一个对象是无关紧要的(特别是因为几乎所有东西都是python中的一个对象,例如int,float等等).可变性至关重要.您可以编写一个适用于列表(可变)的交换函数,但不能编写元组或字符串(不可变).另请参见:http://www.python-course.eu/passing_arguments.php,它在某些情况下清楚地显示了与java不同的行为. (5认同)
  • 我的评论确实不正确,但我认为python语义与java不同,正如在引用的文章中所解释的那样.在python中,函数内部的变化是否在其外部可见其变量取决于可变性,而不是特殊类型(比如java中的对象与原始类型),并且传递'protocol'的参数对于python中的所有内容都是相同的. (4认同)
  • @Claudiu,你是100%正确的,除了Java与附加原语的_行为_没有区别——它只是_确实_有附加原语,而Python没有。当您查看“locals()”和其他一些元内容时,可以看到其他差异 - 作用域以不同的方式实现,并且此实现细节在 Python 中可见。 (3认同)

Dar*_*mas 162

想想通过赋值而不是通过引用/通过值传递的东西.这样,只要您了解正常分配期间发生的情况,就可以清楚地知道发生了什么.

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

a = [1, 2, 3]
b = a
b.append(4)
b = ['a', 'b']
print a, b      # prints [1, 2, 3, 4] ['a', 'b']
Run Code Online (Sandbox Code Playgroud)

由于无法修改不可变类型,它们看起来像是通过值传递 - 将int传递给函数意味着将int赋给函数参数.您只能重新分配,但不会更改原始变量值.

  • 乍一看,这个答案似乎回避了原来的问题.经过第二次阅读后,我意识到这使事情变得非常明确.这里可以找到对这个"名称分配"概念的一个很好的跟进:[代码类似Pythonista:惯用Python](http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html#other -languages具备变量) (3认同)

Ray*_*ger 64

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

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

  • 当您进行诸如的分配时x = 1000,会创建一个字典条目,将当前名称空间中的字符串"x"映射到指向包含一千个整数对象的指针.

  • 使用时更新"x"时x = 2000,将创建一个新的整数对象,并更新字典以指向新对象.旧的一千个对象没有变化(可能存在也可能不存在,具体取决于是否有其他任何东西引用该对象).

  • 当您执行新的分配时y = x,会创建一个新的字典条目"y",该条目指向与"x"条目相同的对象.

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

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

希望能为您澄清问题.

  • @HonestAbe是的,在CPython中,*id()*返回地址.但是在PyPy和Jython等其他python中,*id()*只是一个唯一的对象标识符. (4认同)
  • 我非常感谢开发人员对此的了解.正如pepr的答案所暗示的那样,`id()`函数返回指针(对象引用)的值是真的吗? (2认同)

pep*_*epr 59

从技术上讲,Python总是使用传递引用值.我将重复我的其他答复以支持我的发言.

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

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

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

传递参数的插图示例

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

您可以使用id()内置函数来了解参考值(即目标对象的地址).

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

参考值隐藏在Python中.没有任何显式用户类型用于存储参考值.但是,您可以使用list元素(或任何其他合适的容器类型中的元素)作为引用变量,因为所有容器都将元素存储为对目标对象的引用.换句话说,元素实际上不包含在容器内 - 只有对元素的引用.

  • 发明新术语(例如"按引用值传递"或"按对象调用"没有帮助)."呼叫(值|参考|名称)"是标准术语."参考"是一个标准术语.按值传递引用使用标准术语准确地描述了Python,Java和许多其他语言的行为. (27认同)
  • @cayhorstmann:问题是*Python变量*与其他语言的术语含义不同.这样,*通过引用*调用不适合这里.另外,你如何*确切*定义术语*参考*?非正式地,Python方式可以很容易地描述为传递对象的地址.但它不适合Python的潜在分布式实现. (4认同)
  • 实际上这是通过参考值确认其通过的。虽然这个例子不好,但这个答案+1。 (2认同)
  • 在该引用的末尾有一个脚注,其中显示:_"实际上,**通过对象引用调用**将是一个更好的描述,因为如果传递了一个可变对象,则调用者将看到被调用者所做的任何更改对它..."_我同意你的看法,认为混淆是因为试图使用与其他语言建立的术语.除了语义之外,需要理解的是:字典/命名空间,[名称绑定操作](http://docs.python.org/3.3/reference/executionmodel.html)和名称→指针→对象的关系(就像你已经知道的那样). (2认同)

Lut*_*elt 46

Python中没有变量

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

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

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

例:

a = 1
Run Code Online (Sandbox Code Playgroud)

这会将名称绑定a到包含值1的整数类型的对象.

b = x
Run Code Online (Sandbox Code Playgroud)

这会将名称绑定到b名称x当前绑定的同一对象.之后,名称与名称b无关x.

请参阅Python 3语言参考中的3.14.2节.


因此,在问题中显示的代码中,语句self.Change(self.variable)将名称var(在函数范围内Change)绑定到保存值的对象,'Original'并且赋值var = 'Changed'(在函数体中Change)再次分配相同的名称:到其他对象(即碰巧也会持有一个字符串但完全可能是其他东西.

  • "Python没有变数"是一个愚蠢而令人困惑的口号,我真的希望人们不要再说了...... :(这个答案的其余部分是好的! (18认同)
  • 你还会说Javascript没有变量吗?它们的工作方式与Python相同.此外,Java,Ruby,PHP,......我认为更好的教学技巧是,"Python的变量与C的变量不同." (12认同)
  • 这可能令人震惊,但并不愚蠢.而且我也不认为这也让人感到困惑:它希望能够打开收件人对即将到来的解释的想法并使她变得有用"我不知道他们有什么而不​​是变量"的态度.(是的,您的里程可能会有所不同.) (9认同)
  • 是的,Java有变量.Python,JavaScript,Ruby,PHP等也是如此.你不会在Java中说'int`声明一个变量,但是`Integer`却没有.他们都声明了变量.`Integer`变量是一个对象,`int`变量是一个原语.例如,您通过显示"a = 1"来演示变量的工作原理; b = a; a ++#不会修改b`.在Python中也是如此(使用`+ = 1`,因为Python中没有'++`)! (8认同)
  • “变量”的概念很复杂,而且通常很模糊:**变量是值的容器,由名称标识。**在 Python 中,值是对象,容器是对象(看到问题了吗?)名称实际上是不同的东西。我相信以这种方式获得对变量的 _accurate_ 理解要困难得多。名称和对象的解释看起来更困难,但实际上更简单。 (3认同)
  • 我真的不明白这个答案对于这个问题有什么帮助。没有给出解决方案,只叙述了python的现状。你如何传递一个参数,以便它可以被改变?这里的大多数答案都没有给出解决方案。但这一句否认了第一行中明显内容的存在,因此它脱颖而出。很抱歉投了反对票,但这让我有点生气。 (3认同)

Ama*_*icA 42

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

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

variable = ['Original']
self.Change(variable)      
print variable[0]
Run Code Online (Sandbox Code Playgroud)

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

  • 为少量文本+1,为Python没有传递引用的问题提供必要的解决方法.(作为一个适合此处以及本页任何地方的后续评论/问题:我不清楚为什么python不能像C#那样提供"ref"关键字,它只是将调用者的参数包含在列表中这个,并将函数中对参数的引用视为列表的第0个元素.) (6认同)
  • 尼斯.要通过ref传递,请换入[]的. (4认同)

Kob*_*ohn 37

(编辑 - Blair更新了他非常受欢迎的答案,现在它已经准确了)

我认为值得注意的是,目前投票率最高的帖子(布莱尔康拉德)虽然在结果方面是正确的,但却具有误导性,并且基于其定义是不正确的.虽然有许多语言(如C)允许用户通过引用传递或按值传递,但Python不是其中之一.

David Cournapeau的回答指出了真正的答案,并解释了为什么布莱尔康拉德的帖子中的行为似乎是正确的,而定义则不然.

在Python通过值传递的范围内,所有语言都是按值传递的,因为必须发送一些数据(无论是"值"还是"引用").但是,这并不意味着Python在C程序员会想到它的意义上是通过值传递的.

如果你想要这种行为,布莱尔康拉德的回答很好.但是如果你想知道为什么Python既没有通过价值传递也没有通过引用传递,那么请阅读David Cournapeau的回答.

  • 根本不是所有语言都是按值调用的.在C++或Pascal(当然还有许多其他我不知道的)中,您可以通过引用进行调用.例如,在C++中,`void swap(int&x,int&y){int temp = x; x = y; y = temp; }`将交换传递给它的变量.在Pascal中,使用`var`而不是`&`. (4认同)
  • 我以为我很久以前就回复了这个问题,但是我没有看到它.为了完整 - cayhorstmann误解了我的答案.在大多数人第一次学习C/C++*的术语中,我并没有说一切都是按价值调用*.只是*某些*值被传递(值,名称,指针等)并且Blair原始答案中使用的术语不准确. (2认同)

bob*_*obo 23

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

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' )
Run Code Online (Sandbox Code Playgroud)

  • 是的,但是如果你做x = [2,4,4,5,5],y = x,X [0] = 1,打印x#[1,4,4,5,5]打印y#[1 ,4,4,5,5] (2认同)

Mik*_*zur 18

在这种情况下,var方法中标题的变量Change被赋予一个引用self.variable,并立即为其分配一个字符串var.它不再指向self.variable.下面的代码片段显示,如果你修改的数据结构指向会发生什么varself.variable,在这种情况下的列表:

>>> 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']
>>> 
Run Code Online (Sandbox Code Playgroud)

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


ajk*_*hol 17

Python的pass-by-assignment方案与C++的引用参数选项并不完全相同,但事实证明它与C语言(和其他语言)的参数传递模型非常相似:

  • 不可变参数有效地通过" " 传递.整数和字符串等对象通过对象引用而不是通过复制传递,但是因为无论如何都无法在原地更改不可变对象,效果就像制作副本一样.
  • 可变参数有效地通过" 指针 " 传递.列表和字典之类的对象也通过对象引用传递,这类似于C传递数组的方式作为指针 - 可变对象可以在函数中就地更改,就像C数组一样.


Nun*_*eto 16

你可以说你需要有一个可变对象,但是我建议你检查全局变量,因为它们可以帮助你甚至解决这类问题!

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
Run Code Online (Sandbox Code Playgroud)

  • 我很想发布一个类似的回复 - 原来的提问者可能不知道他想要的实际上是使用全局变量,在函数之间共享.下面是我会分享的链接:http://stackoverflow.com/questions/423379/using-global-variables-in-a-function-other-than-the-one-that-c​​reated-them在回答@Tim ,堆栈溢出不仅是一个问答网站,它的参考知识,只有变得更强,更nuanced-很像更多的输入端的有效wiki-一个巨大的资源库. (5认同)

Joo*_*oop 13

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

"在Python中,仅在函数内引用的变量是隐式全局的.如果在函数体内的任何位置为变量赋值,则假定它是局部的.如果变量在函数内部被赋予了新值,变量是隐式本地的,你需要明确地将它声明为'全局'.虽然起初有点令人惊讶,但是时刻的考虑解释了这一点.一方面,要求全局指定变量可以防止意外的副作用.另一方面,如果所有全局引用都需要全局,那么您将一直使用全局.您必须声明为内置函数或导入模块的组件的全局引用.会破坏全球声明确定副作用的有用性."

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

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)
Run Code Online (Sandbox Code Playgroud)

得到:

Original [1, 2, 3] 4454645632
Received [1, 2, 3] 4454645632
Changed to [0, 0, 0] 4474591928
After [1, 2, 3] 4454645632
Run Code Online (Sandbox Code Playgroud)

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


mat*_*ino 10

以下是pass by objectPython中使用的概念的简单(我希望)解释.
无论何时将对象传递给函数,对象本身都会被传递(Python中的对象实际上是您在其他编程语言中称为值的对象),而不是对该对象的引用.换句话说,当你打电话:

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

my_list = [0, 1]
change_me(my_list)
Run Code Online (Sandbox Code Playgroud)

正在传递实际对象 - [0,1](在其他编程语言中称为值).所以实际上函数change_me会尝试做类似的事情:

[0, 1] = [1, 2, 3]
Run Code Online (Sandbox Code Playgroud)

这显然不会改变传递给函数的对象.如果函数看起来像这样:

def change_me(list):
   list.append(2)
Run Code Online (Sandbox Code Playgroud)

然后调用将导致:

[0, 1].append(2)
Run Code Online (Sandbox Code Playgroud)

这显然会改变对象.这个答案解释得很好.

  • 问题是作业没有执行您期望的其他事情。“ list = [1、2、3]”会导致将“ list”名称重用于其他用途,并忘记最初传递的对象。但是,您可以尝试`list [:] = [1,2,3]`(顺便说一句,`list`是变量的错误名称。考虑`[0,1] = [1,2,3]`完全是胡说八道,无论如何,您认为*对象本身已通过*是什么?您认为复制到函数的内容是什么? (2认同)

Dol*_*nga 8

除了关于这些东西如何在Python中运行的所有重要解释之外,我没有看到针对该问题的简单建议.当您似乎创建对象和实例时,处理实例变量并更改它们的pythonic方法如下:

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

    def Change(self):
        self.variable = 'Changed'
Run Code Online (Sandbox Code Playgroud)

在实例方法中,您通常会引用self访问实例属性.__init__在实例方法中设置实例属性并读取或更改它们是正常的.这也是你传递self第一个参数的原因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
Run Code Online (Sandbox Code Playgroud)


Bra*_*ter 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
Run Code Online (Sandbox Code Playgroud)


itm*_*kel 6

通过引用传递对象有一个小技巧,即使语言不可能.它也适用于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
Run Code Online (Sandbox Code Playgroud)

这是一个丑陋的黑客,但它的工作原理.;-P


Dan*_*our 6

因为似乎没有任何地方提到模拟引用的方法,例如 C++ 中已知的方法是使用“更新”函数并传递它而不是实际变量(或者更确切地说,“名称”):

def need_to_modify(update):
    update(42) # set new value 42
    # other code

def call_it():
    value = 21
    def update_value(new_value):
        nonlocal value
        value = new_value
    need_to_modify(update_value)
    print(value) # prints 42
Run Code Online (Sandbox Code Playgroud)

这对于“仅输出引用”或在具有多个线程/进程的情况下最有用(通过使更新函数线程/多处理安全)。

显然上面不允许读取该值,只能更新它。


mAR*_*ORE 5

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

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

    def change(self, var):
        self.__dict__[var] = 'Changed'
Run Code Online (Sandbox Code Playgroud)

在实际代码中,您当然会在 dict 查找中添加错误检查。


Lia*_*kos 5

由于字典是通过引用传递的,因此您可以使用 dict 变量将任何引用的值存储在其中。

# returns the result of adding numbers `a` and `b`
def AddNumbers(a, b, ref): # using a dict for reference
    result = a + b
    ref['multi'] = a * b # reference the multi. ref['multi'] is number
    ref['msg'] = "The result: " + str(result) + " was nice!"
    return result

number1 = 5
number2 = 10
ref = {} # init a dict like that so it can save all the referenced values. this is because all dictionaries are passed by reference, while strings and numbers do not.

sum = AddNumbers(number1, number2, ref)
print("sum: ", sum)             # the returned value
print("multi: ", ref['multi'])  # a referenced value
print("msg: ", ref['msg'])      # a referenced value
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

1239338 次

最近记录:

5 年,9 月 前