在Python中将一个变量的值赋给另一个变量时会发生什么?

时间:2017-07-12 09:15:49

标签: python variables object

这是我学习python的第二天(我知道C ++和一些OOP的基础知识。),我对python中的变量有些轻微的混淆。

以下是我目前对它们的理解:

Python变量是对象的引用(或指针?)(它们是可变的或不可变的)。当我们有类似num = 5的东西时,不可变对象5在内存中的某处创建,名称 - 对象引用对num在某个名称空间中创建。当我们有a = num时,没有任何内容被复制,但现在两个变量都引用同一个对象,a被添加到同一个名称空间。

这就是我的书使用Python自动化无聊的东西,让我感到困惑。由于它是一本新手书,它没有提到对象,命名空间等,它试图解释以下代码:

>>> spam = 42
>>> cheese = spam
>>> spam = 100
>>> spam
100
>>> cheese
42

它提供的解释与C ++书籍完全相同,我不满意,因为我们正在处理对象的引用/指针。所以在这种情况下,我猜在第3行,因为整数是不可变的,spam被赋予一个全新的指针/引用,指向内存中的不同位置,即它最初指向的内存isn' t修改。因此,我们cheese引用spam引用的初始对象。这是正确的解释吗?

10 个答案:

答案 0 :(得分:79)

作为C ++开发人员,您可以将Python变量视为指针。

因此,当您编写spam = 100时,这意味着您将指向对象42的指针"指向对象100。 }。

早些时候,cheese被指定为指向与spam指向的同一个对象,当时恰好是42。由于您尚未修改cheese,因此仍指向42

在这种情况下,不变性与它无关,因为指针赋值不会改变被指向对象的任何内容。

答案 1 :(得分:20)

我看到它的方式有不同的语言观点。

  • “语言律师”的观点。
  • “实用程序员”的观点。
  • “实施者”的观点。

从语言律师的角度来看,python变量总是“指向”一个对象。但是,与Java和C ++不同,==< => =等的behvaiour取决于变量所指向的对象的运行时类型。此外,在python内存管理由语言处理。

从实际的程序员角度来看,我们可以将整数,字符串,元组等是不可变的*对象而不是直接值作为无关紧要的细节。例外情况是,当存储大量的数值数据时,我们可能希望使用可以直接存储值的类型(例如numpy数组),而不是最终会有一个充满对微小对象的引用的数组的类型。

从实施者的角度来看,大多数语言都有某种假设规则,如果指定的行为是正确的,那么实施是正确的,无论事情是如何实际完成的。

所以,从语言律师的角度来看,你的解释是正确的。从实际程序员的角度来看,你的书是正确的。实现实际上取决于实现。在cpython中,整数是真实对象,尽管从缓存池中获取小值整数而不是重新创建。我不确定其他实现(例如pypy和jython)是做什么的。

*注意这里可变对象和不可变对象之间的区别。对于一个可变对象,我们必须小心处理它“像一个值”,因为其他一些代码可能会改变它。对于不可变对象,我们没有这样的顾虑。

答案 2 :(得分:19)

这是正确的你可以或多或少的变量作为指针。但是,示例代码可以帮助解释 实际上是如何工作的。

首先,我们将大量使用id函数:

  

返回对象的“标识”。这是一个整数,在该生命周期内保证该对象是唯一且恒定的。具有非重叠生存期的两个对象可能具有相同的id()值。

这可能会在您的机器上返回不同的绝对值。

考虑这个例子:

>>> foo = 'a string'
>>> id(foo) 
4565302640
>>> bar = 'a different string'
>>> id(bar)
4565321816
>>> bar = foo
>>> id(bar) == id(foo)
True
>>> id(bar)
4565302640

你可以看到:

  • 原始foo / bar有不同的ID,因为它们指向不同的对象
  • 当bar被分配给foo时,它们的id现在是相同的。这类似于它们都指向你在制作C ++指针时看到的内存中的相同位置

当我们更改foo的值时,它被分配给不同的id:

>>> foo = 42
>>> id(foo)
4561661488
>>> foo = 'oh no'
>>> id(foo)
4565257832

一个有趣的观察结果是整数隐含地具有高达256的这个功能:

>>> a = 100
>>> b = 100
>>> c = 100
>>> id(a) == id(b) == id(c)
True

然而,超过256,这已不再适用:

>>> a = 256
>>> b = 256
>>> id(a) == id(b)
True
>>> a = 257
>>> b = 257
>>> id(a) == id(b)
False

然而,将a分配给b确实会使ID与之前显示的相同:

>>> a = b
>>> id(a) == id(b)
True

答案 3 :(得分:16)

Python既不是传递引用,也不是按值传递。 Python变量不是指针,它们不是引用,它们不是值。 Python variables are names

如果您需要相同的短语类型,或者可能是“按对象传递”,则将其视为“别名传递”,因为您可以从指示它的任何变量中变异相同的对象,如果它是可变的,但重新分配变量(别名)只会改变一个变量。

  

如果有帮助:C变量是您将值写入的框。 Python名称是您放在值上的标记。

Python变量的名称是全局(或本地)命名空间中的一个键,它实际上是一个字典。底层值是内存中的某个对象。赋值给该对象命名。将一个变量赋值给另一个变量意味着两个变量都是同一个对象的名称。重新分配一个变量会更改该变量命名的对象,而不会更改其他变量。您已移动标记但未更改上一个对象或其上的任何其他标记。

在CPython实现的底层C代码中,每个Python对象都是PyObject*,所以如果你只有指向数据的指针(没有指针到指针,你可以认为它就像C一样工作)没有直接传递的值。)

  

你可以说Python是值传递,值是指针......或者你可以说Python是传递引用,其中引用是副本。

答案 4 :(得分:10)

当您运行spam = 100 python时,在内存中再创建一个对象但不更改现有对象。所以你仍然有指针cheese到42和spam到100

答案 5 :(得分:8)

spam = 100行中发生的事情是用另一个指向另一个对象的指针(类型int替换先前的值(指向类型为42的对象的指针,值为int) ,值100

答案 6 :(得分:8)

正如@DeepSpace在评论中提到的那样,Ned Batchelder在博客中揭示变量(名称)和值的分配方面做得很好,他在PyCon 2015上发表了演讲,Facts and Myths about Python names and values。它可以让任何关注程度的Pythonist都具有洞察力。

答案 7 :(得分:1)

存储spam = 42时,它会在内存中创建一个对象。然后您指定cheese = spam,它将spam引用的对象分配给cheese。最后,当您更改spam = 100时,它只会更改spam个对象。所以cheese = 42

答案 8 :(得分:1)

在Python中,变量保存对对象引用对象是分配的内存块,其中包含一个值和一个 header 。对象的标头包含其类型和引用计数器,该计数器指示在源代码中引用该对象的次数,以便垃圾回收可以识别是否可以收集对象。

现在,当您将值分配给变量时,Python实际上将引用分配给分配给对象的内存位置 pointers

# x holds a reference to the memory location allocated for  
# the object(type=string, value="Hello World", refCounter=1)

x = "Hello World" 

现在,当您将不同类型的对象分配给同一变量时,您实际上会更改引用,使其指向不同的对象(即,不同的内存位置)。到您为变量分配不同的引用(即对象)时,垃圾回收器将立即回收分配给前一个对象的空间,前提是源代码中的任何其他变量都未引用该对象:

# x holds a reference to the memory location allocated for  
# the object(type=string, value="Hello World", refCounter=1)

x = "Hello World" 

# Now x holds the reference to a different object(type=int, value=10, refCounter=1)
# and object(type=string, value="Hello World", refCounter=0) -which is not refereced elsewhere
# will now be garbage-collected.
x = 10

现在来看您的示例

spam保留对object(type = int,value = 42,refCounter = 1)的引用:

>>> spam = 42

现在cheese还将保留对object(type = int,value = 42,refCounter = 2)的引用

>>> cheese = spam

现在,垃圾邮件拥有对另一个对象的引用(类型=整数,值= 100,refCounter = 1)

>>> spam = 100
>>> spam
100

但是奶酪将继续指向对象(类型=整数,值= 42,refCounter = 1)

>>> cheese
42

答案 9 :(得分:-1)

numpy.copy()函数页面上有说明

https://docs.scipy.org/doc/numpy/reference/generated/numpy.copy.html

它给出的示例如下:

创建一个数组x,其引用为y,副本为z:

x = np.array([1, 2, 3])
y = x
z = np.copy(x)

请注意,当我们修改x时,y会更改,但z不会:

x[0] = 10
x[0] == y[0]
True
x[0] == z[0]
False