如何通过引用传递变量?

2024-11-15 08:36:00
admin
原创
15
摘要:问题描述:我编写了这个类来进行测试:class PassByReference: def __init__(self): self.variable = 'Original' self.change(self.variable) print(self.v...

问题描述:

我编写了这个类来进行测试:

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

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

当我尝试创建一个实例时,输出为Original。因此,Python 中的参数似乎是按值传递的。对吗?我该如何修改代码以获得按引用传递的效果,以便输出为Changed


有时人们会惊讶于像 这样的代码x = 1(其中x是参数名称)不会影响调用者的参数,而像 这样的代码x[0] = 1却会影响。发生这种情况是因为尽管语法不同,但项赋值切片赋值是改变现有对象的方式,而不是重新分配变量=。有关详细信息,请参阅为什么函数可以修改调用者认为的某些参数,但不能修改其他参数?。

另请参阅按引用传递和按值传递有什么区别?以了解重要的、与语言无关的术语讨论。


解决方案 1:

参数通过赋值传递。这背后的理由有两点:

  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指向的位置。

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

尽管这看上去有些麻烦。

解决方案 2:

问题源于对 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'

解决方案 3:

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

解决方案 4:

它既不是按值传递也不是按引用传递,而是按对象调用。请参阅 Fredrik Lundh 的文章:

通过对象调用

以下是一段意义重大的引言:

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

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

解决方案 5:

考虑通过赋值传递内容,而不是通过引用/值传递内容。这样,只要您了解正常赋值过程中发生的情况,就会始终清楚发生了什么。

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

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:

Python 中没有变量

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

  1. Python 有名称和对象。

  2. 赋值将名称绑定到对象。

  3. 将参数传递给函数也会将名称(函数的参数名称)绑定到对象。

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

例子:

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)再次分配相同的名称:给其他对象(恰好也保存一个字符串,但可能是完全不同的东西)。

如何通过引用传递

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

如果它是一个不可变对象(例如布尔值、数字、字符串),那么要做的事情就是将其包装在可变对象中。

对此,快速而粗略的解决方案是使用单元素列表(而不是self.variable,传递[self.variable]并在函数中修改var[0])。

更符合Python风格的方法是引入一个简单的单属性类。该函数接收该类的实例并操作该属性。

解决方案 7:

Effbot(又名 Fredrik Lundh)将 Python 的变量传递风格描述为按对象调用: https://web.archive.org/web/20201111195827/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”都指向同一个列表。append方法会改变(更新)列表对象(如向数据库添加记录),并且结果对“x”和“y”都可见(就像数据库更新对该数据库的每个连接都可见一样)。

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

解决方案 8:

从技术上讲,Python 始终使用传递引用值。我将重复我的其他答案来支持我的陈述。

Python 总是使用传递引用值。没有任何例外。任何变量赋值都意味着复制引用值。没有例外。任何变量都是与引用值绑定的名称。始终如此。

您可以将引用值视为目标对象的地址。使用时,地址会自动取消引用。这样,使用引用值时,您似乎直接使用目标对象。但两者之间总是存在一个引用,需要再走一步才能跳转到目标。

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

传递参数的图示示例

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

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

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

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

解决方案 9:

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

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

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

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

解决方案 10:

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

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

解决方案 11:

var在这种情况下,方法中titled 的变量Change被赋予了对 的引用self.variable,并且您立即将一个字符串分配给var。它不再指向。以下代码片段显示了如果修改和指向的数据结构(在本例中为列表)self.variable会发生什么:var`self.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']
>>> 

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

解决方案 12:

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

  • 不可变参数实际上是通过“按值”传递的。整数和字符串等对象是通过对象引用传递的,而不是通过复制,但因为你无论如何都无法更改不可变对象,所以效果就像复制一样。

  • 可变参数实际上是通过“指针”传递的。列表和字典等对象也是通过对象引用传递的,这与 C 将数组作为指针传递的方式类似 - 可变对象可以在函数中就地更改,就像 C 数组一样。

解决方案 13:

这里的答案有很多见解,但我认为这里没有明确提到另外一点。引用 Python 文档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)

给出:

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

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

解决方案 14:

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

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:

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)

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

解决方案 16:

除了关于这些东西在 Python 中如何工作的所有出色解释之外,我没有看到针对该问题的简单建议。由于您似乎确实创建了对象和实例,因此处理实例变量和更改它们的 Pythonic 方式如下:

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

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

在实例方法中,通常引用 来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

解决方案 17:

我使用以下方法快速将一些 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

解决方案 18:

要模拟通过引用传递对象,请将其包装在单项列表中:

class PassByReference:
    def __init__(self, name):
        self.name = name

def changeRef(ref):
    ref[0] = PassByReference('Michael')

obj = PassByReference('Peter')
print(obj.name)

p = [obj]
changeRef(p)

print(p[0].name)

给列表元素赋值会改变列表,而不是重新分配名称。由于列表本身具有引用语义,因此更改会反映在调用者身上。

解决方案 19:

由于似乎没有提到模拟引用的方法是使用“更新”函数并传递该函数而不是实际变量(或者更确切地说是“名称”):

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

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

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

解决方案 20:

鉴于 Python 处理值及其引用的方式,引用任意实例属性的唯一方法是通过名称:

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

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

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

解决方案 21:

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

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'

解决方案 22:

您可以仅使用空类作为实例来存储引用对象,因为内部对象属性存储在实例字典中。请参阅示例。

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)

解决方案 23:

由于字典是通过引用传递的,因此您可以使用 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

解决方案 24:

虽然通过引用传递不太适合 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

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

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 文档建议更改字典可能不起作用,但对我来说似乎有效。

解决方案 25:

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

  • Java 和 C#:基本类型(包括字符串)按值传递(复制)。引用类型按引用传递(地址复制),因此调用函数中对参数所做的所有更改对调用者都是可见的。

  • C++:允许按引用传递或按值传递。如果参数按引用传递,您可以修改它或不修改它,具体取决于参数是否作为 const 传递。但是,无论是否为 const,参数都会保留对对象的引用,并且不能将引用赋值给被调用函数内的其他对象。

  • Python:
    Python 是“通过对象引用传递”的,人们常说:“对象引用是通过值传递的。”(阅读此处)。调用者和函数都引用同一个对象,但函数中的参数是一个新变量,它只是保存了调用者中对象的副本。与 C++ 一样,函数中的参数可以修改也可以不修改。这取决于传递的对象类型。例如,不可变对象类型不能在被调用函数中修改,而可变对象可以更新或重新初始化。

更新或重新分配/重新初始化可变变量之间的一个关键区别是,更新的值会反映在被调用的函数中,而重新初始化的值则不会。在 Python 中,将新对象分配给可变变量的范围是函数的本地范围。@blair-conrad 提供的示例非常适合理解这一点。

解决方案 26:

我发现其他答案有点令人困惑,我不得不努力一段时间才能理解这些概念。所以,我试着用我的语言来回答。如果其他答案也让你感到困惑,这可能会对你有所帮助。所以,答案是这样的-

当你创建列表时

my_list = []

您实际上正在创建类列表的对象:

my_list = list()

这里,my_list 只是给予 'list' 类的构造函数创建的对象的内存地址(例如,140707924412080)的名称。

当您将此列表传递给定义为

def my_method1(local_list):
    local_list.append(1)

创建了对同一内存地址 140707924412080 的另一个引用。因此,当您使用 append 方法对对象进行任何更改/变异时,它也会反映在 my_method1 之外。因为外部列表 my_list 和 local_list 都引用相同的内存地址。

另一方面,当你将相同的列表传递给以下方法时,

def my_method2(local_list2):
    local_list2 = [1,2,3,4]

该过程的前半部分保持不变。即,创建了指向相同内存地址 140707924412080 的新引用/名称 local_list2。但是,当您创建一个新的列表 [1,2,3,4] 时,将再次调用“list”类的构造函数并创建一个新对象。这个新对象具有完全不同的内存地址,例如 140707924412112。当您将 local_list2 分配给 [1,2,3,4] 时,现在 local_list2 名称引用一个新的内存地址,即 140707924412112。由于在整个过程中您没有对放置在内存地址 140707924412080 的对象进行任何更改,因此它不受影响。

换句话说,它秉承了“其他语言有变量,Python 有名称”的精神。这意味着在其他语言中,变量被引用到内存中的固定地址。这意味着,在 C++ 中,如果你通过以下方式重新分配变量

a = 1
a = 2

存储值“1”的内存地址现在保存的是值“2”,因此,值“1”完全丢失了。而在 Python 中,由于一切都是对象,之前的“a”指的是存储类“int”对象的内存地址,而该类对象又存储值“1”。但是,在重新分配之后,它指向一个完全不同的内存地址,该地址存储了新创建的类“int”对象,该对象保存值“2”。

希望有帮助。

解决方案 27:

我刚接触 Python,昨天才开始学(虽然我已经编程 45 年了)。

我来这里是因为我正在编写一个函数,其中我想要两个所谓的输出参数。如果只有一个输出参数,我现在就不会纠结于检查 Python 中的引用/值是如何工作的。我只会使用函数的返回值。但由于我需要两个这样的输出参数,我觉得我需要理清它。

在这篇文章中,我将展示我是如何解决我的问题的。也许其他人会觉得它很有价值,尽管它并不是主题问题的确切答案。经验丰富的 Python 程序员当然已经知道我使用的解决方案,但这对我来说是新的。

从这里的答案中,我很快发现 Python 在这方面的工作方式有点像 JavaScript,如果您想要参考功能,则需要使用变通方法。

但后来我在 Python 中发现了一些巧妙的东西,我认为我以前从未在其他语言中见过,即你可以从一个函数返回多个值,以简单的逗号分隔的方式,如下所示:

def somefunction(p):
    a = p + 1
    b = p + 2
    c = -p
    return a, b, c

你也可以在调用方以类似的方式处理这个问题,就像这样

x, y, z = somefunction(w)

这对我来说已经足够好了,我很满意。没有必要使用其他解决方法。

在其他语言中,您当然也可以返回许多值,但通常以对象的形式,并且您需要相应地调整调用方。

Python 的实现方式非常好并且简单。

如果您想要进一步通过引用进行模仿,可以执行以下操作:

def somefunction(a, b, c):
    a = a * 2
    b = b + a
    c = a * b * c
    return a, b, c

x = 3
y = 5
z = 10
print(F"Before : {x}, {y}, {z}")

x, y, z = somefunction(x, y, z)

print(F"After  : {x}, {y}, {z}")

得出这个结果

之前:3、5、10
之后 : 6, 11, 660

解决方案 28:

或者,您可以使用如下所示的ctypes :

import ctypes

def f(a):
    a.value = 2398 ## Resign the value in a function

a = ctypes.c_int(0)
print("pre f", a)
f(a)
print("post f", a)

因为 a 是 ac int,而不是 Python 整数,显然是通过引用传递的。但是,您必须小心,因为可能会发生奇怪的事情,因此不建议这样做。

解决方案 29:

使用数据类。此外,它还允许您应用类型限制(又名“类型提示”)。

from dataclasses import dataclass

@dataclass
class Holder:
    obj: your_type # Need any type? Use "obj: object" then.

def foo(ref: Holder):
    ref.obj = do_something()

我同意大家的观点,在大多数情况下最好考虑不要使用它。

然而,当我们谈论上下文时,了解这一点是值得的。

不过,您可以设计一个显式的上下文类。在设计原型时,我更喜欢数据类,因为它很容易来回序列化它们。

解决方案 30:

关于这个问题已经有很多很好的答案(或者说是意见),我已经读过了,但我想提一下遗漏的一个。它来自Python 文档的FAQ 部分。我不知道发布此页面的日期,但这应该是我们真正的参考:

请记住,在 Python 中参数是通过赋值传递的。由于赋值只是创建对对象的引用,因此调用方和被调用方的参数名称之间没有别名,因此本质上不存在引用调用。

如果您有:

a = SOMETHING

def fn(arg):
    pass

并且你将其称为fn(a),你所做的就是你在任务中所做的。因此发生了以下事情:

arg = a

创建了对的附加引用SOMETHING。变量只是符号/名称/引用。它们不“保存”任何东西。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用