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


80

这是我学习python的第二天(我了解C ++和一些OOP的基础知识),并且我对python中的变量有些困惑。

这是我目前对它们的了解:

Python变量是对对象(可变或不可变)的引用(或指针?)。当我们有类似的内容时num = 5,不可变对象5将在内存中的某个位置创建,而名称-对象引用对num将在某个命名空间中创建。当我们拥有时a = num,不会复制任何内容,但是现在两个变量都引用相同的对象,a并被添加到相同的名称空间。

这就是我的书《用Python自动处理无聊的东西》,令我感到困惑。由于它是一本新手书籍,因此没有提及对象,名称空间等,而是尝试解释以下代码:

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

它提供的解释与C ++书籍的解释完全相同,当我们处理对象的引用/指针时,我对此并不满意。因此,在这种情况下,我想在第三行中,由于整数是不可变的,spam因此正在为内存中的其他位置分配一个全新的指针/引用,即,它最初指向的内存没有被修改。因此,我们已经cheese提到了由引用的初始对象spam。这是正确的解释吗?



5
您储存42spam,现在您储存spam在奶酪里cheese = 42,之后替换spam100,您没有编辑cheese,这就是为什么cheese仍然42

15
正如乔纳斯(Jonas)在他的回答中指出的那样,不变性在这里是一条红鲱鱼:如果使用可变对象(如列表),您将获得完全相同的结果。关键是您变量重新分配给新引用。
丹尼尔·罗斯曼


27
@mini:我更喜欢将Python变量视为标签而不是存储桶。您将标签粘贴spam在数字42上。接下来,您将标签粘贴cheese在标记为的东西上spam(请注意,不要在标签本身的顶部)。然后你去皮的spam标签,把这一点,并把它放在100号
蒂姆Pederick

Answers:


81

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

因此,在编写时spam = 100,这意味着您“分配了指针”(以前指向该对象42)以指向该对象100

较早时,cheese被分配指向与所指对象相同的对象spam,该对象恰好42在那时。由于您尚未修改cheese,因此仍指向42

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


2
与js中的对象相同
Dushyant Bangal,2017年

7
不变性很重要,因为它意味着您可以安全地将引用视为值。将易变的对象视作有价值的东西将具有更高的风险。
plugwash

21

我的看法有一种语言的不同看法。

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

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

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

从实现者的角度来看,大多数语言都有某种“假设”规则,因此,如果指定的行为正确无误,则无论幕后实际上是如何完成的,实现都是正确的。

因此,从语言律师的角度来看,您的解释是正确的。从实用的程序员角度来看,您的书是正确的。实现实际执行的操作取决于实现。在cpython中,整数是实际对象,尽管小值整数是从缓存池中获取的,而不是重新创建的。我不确定其他实现(例如pypy和jython)的作用。

*请注意此处可变对象与不可变对象之间的区别。对于可变对象,我们必须谨慎对待它“就像一个值”,因为其他一些代码可能会使它变异。对于一个不变的对象,我们没有这样的担忧。


20

可以将变量或多或少作为指针是正确的。然而示例代码将与解释大大有助于怎么这实际上是工作。

首先,我们将大量利用该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

但是,分配ab确实会使ID与之前显示的相同:

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

18

Python既不是按引用传递也不是按值传递。Python变量不是指针,不是引用,不是值。Python变量是名称

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

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

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

在CPython实现的基础C代码中,每个Python对象都是一个PyObject*,因此,如果您仅拥有指向数据的指针(没有指向指针的指针,没有直接传递的值),则可以将其视为C。

您可以说Python是按值传递,其中值是指针……,也可以说Python是按引用传递,其中引用是副本。


1
称其为“ pass-by-name”的问题在于,已经存在一个名为“ by by name”的参数传递约定,其含义完全不同。在按名称调用中,每次函数使用参数时都会评估参数表达式,如果函数不使用参数就不会评估参数表达式。
user2357112支持Monica17年


8

spam = 100在线发生的事情是用另一个指向另一个对象(type ,value )的指针替换了先前的值(指向类型int为value的对象的42指针)int100


整数是堆栈上的值对象吗?
Gert Kommer

是的,它们类似于您new Class()在C ++中使用语法创建的对象。而且,在Python中,任何事物都是object类/子类的实例。
bakatrouble

4
至少在CPython中@GertKommer,所有对象都驻留在堆中。没有“值对象”的区别。只有对象,一切都是对象。这就是为什么典型的int的大小约为28个字节(取决于Python版本)的原因,因为它具有整个Py_Object的开销。Small-int被缓存为CPython优化。
juanpa.arrivillaga

8

正如@DeepSpace在评论中提到的那样,Ned Batchelder在博客中为变量(名称)和值赋值的神秘化做了出色的工作,他在PyCon 2015上发表了有关Python名称和值的事实和神话。对于任何精通Python的人来说,它都是有见地的。


1

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

现在,当您为变量分配值时,Python实际上会分配引用,这些引用指向分配给对象的内存位置的指针

# 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 保存对对象的引用(类型=整数,值= 42,refCounter = 1):

>>> spam = 42

现在cheese还将保留对对象的引用(类型=整数,值= 42,refCounter = 2)

>>> cheese = spam

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

>>> spam = 100
>>> spam
100

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

>>> cheese
42

0

当您存储时spam = 42,它将在内存中创建一个对象。然后分配cheese = spam,它分配引用spam到的对象cheese。最后,当您更改时spam = 100,它仅更改spam对象。这样cheese = 42


7
“然后,您分配起司=垃圾邮件,它将在内存中创建另一个对象”不,不是。它将引用的对象分配spamcheese。没有创建新对象。
juanpa.arrivillaga

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.