蟒蛇的文件似乎不清楚是否参数是通过基准或价值,以下代码产生的不变价值"原始"

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

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

有什么我可以做的通过变量按实际参考?

有帮助吗?

解决方案

参数是 通过分配. 。这背后的理由有两个:

  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 参数是按值传递的,为其分配一个新列表对于方法外部的代码可以看到没有任何影响。这 the_list 是一个副本 outer_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)

输出:

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

虽然这看起来有点麻烦。

其他提示

这个问题是从一个误区是什么变量在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把变量和参数的方式。 “在这里输入图像描述“

它既不是通过价值或通过参考--它是通话的对象。看看这个,弗雷德里克*Lundh:

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

这是一个显着的话:

"...变量[名称]是 对象;他们不能表示通过其他变量或提到的对象。"

在你的例子,当时的 Change 方法被称为一-- namespace 创建;和 var 成为一个名称,在这名字空间,用于串的对象 'Original'.那个对象然后有一个名称在两名字空间。接下来, var = 'Changed' 结合 var 一个新的串的对象,并因此该方法的名字空间的忘记了 'Original'.最后,该名称空间是被遗忘,并字符串 'Changed' 随着它。

的东西被认为/传递按分配而不是通过由参考值。这样一来,它是永诺清楚,只要你了解正常分配期间发生了什么发生了什么。

因此,传递一个列表的功能/方法时,该列表被分配给参数名称。追加到列表将导致在列表中进行修改。重新分配列表 内的功能不会改变原始列表,因为:

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

由于不可变的类型不能被修改,他们似乎像由值被传递 - 通过一个int成一个函数是指分配的int的功能参数。你永远只能重新分配,但它不会改变originial变量的值。

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

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

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

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

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

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

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

希望能为您澄清这个问题。

从技术上讲,的的Python总是通过参考值使用通行证。我要重复我的其他答案来支持我的说法。

的Python始终使用通过按参考值。没有任何例外。任何变量赋值装置复制的参考值。没有例外。任何变量是结合到所述参考值的名称。总是

可以想到的基准值作为目标对象的地址。使用时,地址将被自动取消引用。这样一来,与参考值的工作,看来你直接与目标对象的工作。但总是处于之间的参考,一个步骤更跳转到目标

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

“传递参数的示出的示例”

如果该参数是由值来传递,外lst不能被修改。绿色是目标对象(黑色是内部存储的值,红色是对象类型),黄色与内部参考值的存储器 - 绘制为箭头。该蓝色固体箭头是被传递给函数(通过虚线蓝色箭头路径)的参考值。丑陋的暗黄色是内部字典。 (它实际上可被也绘制成绿色椭圆,颜色和形状仅说,这是内部的。)

您可以使用 id() 内置功能学什么参考值是(即,目标对象的地址)。

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

参考值被隐藏在Python。不存在用于存储所述基准值的任何显式用户类型。然而,可以使用一个列表元素(或元件以任何其他合适的容器类型)作为参考变量,因为所有容器做还存储所述元素作为给目标对象的引用。换句话说,元素实际上没有包含在容器内部 - 只有引用元素是

一个简单的一招我通常使用是刚刚把它包装在一个列表中:

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

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

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

Python中没有变量

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

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

这就是全部内容了。可变性与这个问题无关。

例子:

a = 1

这绑定了名字 a 到保存值 1 的整数类型的对象。

b = x

这绑定了名字 b 与名称相同的对象 x 目前已绑定到。后来,名字 b 与名字无关 x 不再有。

参见章节 3.14.2 在 Python 3 语言参考中。

如何阅读问题中的示例

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

如何通过引用传递

(2019-04-28 编辑)

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

如果它是一个 不可变的 对象(例如一个布尔值、数字、字符串),正确的方法是将其包装在一个可变对象中。
对此的快速而肮脏的解决方案是一个单元素列表(而不是 self.variable, , 经过 [self.variable] 并在函数中修改 var[0]).
越多 蟒蛇式的 一种方法是引入一个简单的单属性类。该函数接收类的实例并操作属性。

(编辑 - 布莱尔已经更新了他非常流行的答案,以便它现在是准确的)

我觉得需要注意的是目前的职位得票最多(布莱尔康拉德),而可相对于它的结果是正确的,是误导性的,是不正确边缘基于它的定义是很重要的。虽然有许多语言(如C),其允许用户或者通过引用传递或传值,Python是不是其中之一。

大卫Cournapeau的答案指向真正的答案和解释了为什么在布莱尔康拉德后的行为似乎同时定义是不正确。

要Python是传值的范围内,所有的语言都通过值通因为某些数据块(可以是一个“值”或“参考”)必须被发送。然而,这并不意味着Python是传值在C程序员会认为它的意义。

如果你想要的行为,布莱尔康拉德的答案是罚款。但是,如果你想知道的螺母和螺栓为什么Python的既不是按值传递或按引用传递,阅读大卫Cournapeau的答案。

您得到了一些很好的答案在这里。

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

在这种情况下,在该方法中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']
>>> 

我相信别人能澄清这进一步。

Python 的传递赋值方案与 C++ 的引用参数选项不太一样,但在实践中它与 C 语言(和其他语言)的参数传递模型非常相似:

  • 不可变的参数被有效地传递“按价值。”诸如整数和字符串之类的对象是通过对象引用而不是通过复制传递的,但是由于您无法将不变的对象更改到位,因此效果就像制作副本一样。
  • 可变参数被有效地传递“通过指针。”列表和词典之类的对象也通过对象引用传递,该对象类似于C传递数组作为指示器的方式 - 可以在功能中更改宽敞的对象,就像C数组一样。

你可以你需要有一个可变的目的,但是,让我建议你查过的全球变量,因为他们可以帮助你或甚至解决这样的问题!

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

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

“在 Python 中,仅在函数内部引用的变量是隐式全局的。如果在函数体内的任何位置为变量分配了新值,则假定该变量是局部变量。如果在函数内为变量分配了新值,则该变量是隐式局部变量,您需要将其显式声明为“全局”。虽然一开始有点令人惊讶,但稍加思考就解释了这一点。一方面,要求全局分配变量可以防止意外的副作用。另一方面,如果所有全局引用都需要 global,那么您将一直使用 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

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

下面是在Python中使用的概念pass by object的简单(希望)的解释。结果 当你传递一个对象的功能,对象本身传递(在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)

这显然将改变对象。 此答案解释很清楚。

除了这个东西在Python是如何工作的所有伟大的解释,我没有看到问题的一个简单的建议。你似乎做创建对象和实例,处理实例变量,并改变它们的Python的方式如下:

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

    def Change(self):
        self.variable = 'Changed'

在实例方法时,通常是指self访问实例属性。这是正常的设置__init__实例属性和读取或更改它们的实例方法。这也是为什么你传递self ALS到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

有一个小窍门通过引用传递的对象,即使语言不会使之成为可能。它的工作原理在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

我用下面的方法来几个的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

虽然通过引用传递的是什么,很符合蟒蛇,应该是很少使用也有一些解决办法,实际上可以工作得到当前分配到一个局部变量的对象,甚至从调用函数的内部重新分配局部变量。

的基本思想是为具有能够做到这一点的访问并且可以作为对象插入其他功能被传递或存储在一个类的功能。

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

def change(wrapper):
    wrapper(7)

x = 5
def setter(val):
    global x
    x = val
print(x)

同样的想法适用于读取和deleting的变量。

有关刚刚读取存在甚至只是使用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

蟒“反射”的支持使得能够得到一个对象,它是它能够重新分配在给定的范围的名称/变量,而不在该范围显式定义的函数:

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和局部变量名的结果,这结束了访问本地变量。 Python文档为3.5建议,改变字典可能无法正常工作,但它似乎为我工作。

给出蟒处理值和引用它们的方式,可以参考任意实例属性的唯一途径是通过名称:

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

    def change(self, var):
        self.__dict__[var] = 'Changed'

在实际的代码,你会的,当然,添加错误的字典查找检查。

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

  • Java&C#: 基本类型(包括字符串)按值传递(复制),引用类型按引用传递(地址复制),因此调用者可以看到被调用函数中参数中所做的所有更改。
  • C++: 允许按引用传递或按值传递。如果参数是通过引用传递的,则可以修改它也可以不修改它,具体取决于该参数是否作为 const 传递。但是,无论是否为 const,参数都会维护对对象的引用,并且不能将引用分配为指向被调用函数内的不同对象。
  • Python: Python 是“按对象引用传递”,人们常说:“对象引用是按值传递的。”[阅读此处]1. 。调用者和函数都引用同一个对象,但函数中的参数是一个新变量,它仅保存调用者中对象的副本。与 C++ 一样,参数可以在函数中修改,也可以不修改 - 这取决于传递的对象的类型。例如;不可变对象类型不能在被调用函数中修改,而可变对象可以更新或重新初始化。更新或重新分配/重新初始化可变变量之间的一个关键区别在于,更新的值会反映回被调用的函数中,而重新初始化的值则不会。新对象对可变变量的任何赋值的范围对于 python 中的函数来说都是本地的。@blair-conrad 提供的示例非常有助于理解这一点。

由于您的例子恰好是面向对象的,你可以做以下的变化来获得类似的结果:

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'

由于它似乎从例如已知无处提及以模拟的引用的方法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

这是最有用,用于“仅引用”或在具有多个线程/进程的情况下(通过使更新功能线程/多处理安全的)。

显然上述不允许读取的值,仅更新它。

可以仅使用一个空类作为一个实例来存储参考对象,因为在内部对象的属性存储在一个实例字典。见的例子中

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)

由于词典都通过引用传递,则可以使用一个字典变量来存储在其内部的任何引用的数值。

# 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!" # reference any string (errors, e.t.c). ref['msg'] is string
    return result # return the sum

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 return value
print("multi: ", ref['multi'])  # a referenced value
print("msg: ", ref['msg'])      # a referenced value
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top