我到处听说变量是 Python 中的“名称,而不是存储”,重要的是不要将它们视为存储,但我还没有找到一个例子来说明为什么这很重要。所以问题是,为什么区分变量是名称和变量是存储很重要?
3 回答
a = SomeObject()
b = a
如果名称是存储(例如在 C 和 C++ 中),那么两者a
和b
都将包含一个对象:
a +---------+
| value 1 |
+---------+
b +---------+
| value 2 |
+---------+
因此,例如,a.x = ...
将对值 1 进行操作,而值 2 则完全不涉及。请注意,这样做的语言提供的值允许通过另一个值操作一个值(例如指针)。但是,这与本主题无关,您可以在 Python 的模型中做类似的事情。
在 Python 和类似语言中,内存看起来更像这样:
a +-------------+
| reference 1 | ---------+
+-------------+ v
+---------+
| value 1 |
+---------+
b +-------------+ ^
| reference 2 | ---------+
+-------------+
这里的引用是一个虚构的标记,它指的是(duh!)对象。任何对象都可以有任意数量的引用,对象不知道其中任何一个,如果没有对它的引用,对象仍然可以徘徊。还要注意,变量并不是唯一可以弹出引用的地方——列表包含引用,字典包含引用,对象的属性包含引用等。它有点像 C 中的指针,只是它不是可识别的值,更不用说对象了,在语言中(因此也不存在与指针算术等价的东西)。
最明显的后果是变量可以别名,因此value #1
通过一个的突变可以通过另一个看到:
a.something = 1
b.something = 2
assert a.something == 2
变量的重新分配不是值 1 的突变,它只是改变了引用。也就是说,a = ...
不影响b
,反之亦然!
不像 C 语言,变量“包含”数据。
在 Python 中,名称是对数据存储位置的引用。
所以,使用列表(可变)
>>> x = [10]
>>> y = x
>>> id(x) == id(y) # they refer to the same object
True
>>> y.append(1) # manipulate y
>>> x # x is manipulated
[10, 1]
>>> y # and so is y.
[10, 1]
并带有字符串(不可变)
>>> x = '10'
>>> y = x
>>> id(x) == id(y)
True
>>> y += '1' # manipulate y
>>> id(x) == id(y) # the ids are no longer equal
False
>>> x # x != y
'10'
>>> y
'101'
当你del
是一个变量时,你删除了对对象的引用,当一个对象有 0 个引用时,它被垃圾收集。
这种东西通常用画框和箭头来解释:
C:
pos = { .x = 1, .y = 2 }
------- -----------
| pos |----------->| x:1 y:2 |
------- -----------
pos2 = pos
------- -----------
| pos |----------->| x:1 y:2 |
------- -----------
------- -----------
| pos2|----------->| x:1 y:2 |
------- -----------
pos2.x = 9
------- -----------
| pos |----------->| x:1 y:2 |
------- -----------
------- -----------
| pos2|----------->| x:9 y:2 |
------- -----------
Python:
pos = { 'x':1, 'y': 2 }
------- ----------- -----------
| pos |----------->| 0xabcde |------->| x:1 y:2 |
------- ----------- -----------
pos2 = pos
------- -----------
| pos |----------->| 0xabcde |--\
------- ----------- | -----------
|---->| x:1 y:2 |
------- ----------- | -----------
| pos2|----------->| 0xabcde |--/
------- -----------
pos2.x = 9
------- -----------
| pos |----------->| 0xabcde |--\
------- ----------- | -----------
|---->| x:9 y:2 |
------- ----------- | -----------
| pos2|----------->| 0xabcde |--/
------- -----------
也就是说,python 变量本质上是指针。它们不包含“值”,而是值的地址。当您将一个变量分配给另一个变量时,您只是在复制地址。当你改变一个变量时,你实际上是在改变它的基础值。
@delnan 的画更好(?),但他们错过了一个重要的点:
python中的变量不是抽象的“名称”。它们确实有值,这些值不是神秘的“引用”,它们是非常具体的内存地址。对变量的每次访问都涉及双重间接:首先我们获得一个变量的值(这是一个地址),其次,我们查看谁“生活”在这个地址上。
请注意,python 在这方面并不是唯一的,其他“脚本”语言使用类似的机制。