如果两个变量指向同一个对象,为什么重新分配一个变量不会影响另一个变量?
If two variables point to the same object, why doesn't reassigning one variable affect the other?
我正在尝试了解变量在 python 中的工作原理。假设我有一个对象存储在变量 a
:
中
>>> a = [1, 2, 3]
如果我将 a
分配给 b
,两者都指向同一个对象:
>>> b = a
>>> b is a
True
但如果我重新分配 a
或 b
,那将不再正确:
>>> a = {'x': 'y'}
>>> a is b
False
这两个变量现在有不同的值:
>>> a
{'x': 'y'}
>>> b
[1, 2, 3]
我不明白为什么变量现在不同了。为什么 a is b
不再正确?谁能解释一下这是怎么回事?
在Python中,所有变量都存储在字典中,或者是看起来很像字典的结构(例如locals()
可以将当前的scope/namespace公开为字典)。
注意:PyObject*
是一个CPython概念。我不确定在其他 Python 实现中是如何工作的。
因此,查看 Python 变量如 C 的变量具有精确的内存位置是有缺陷的。它们的值是PyObject*
(指针或内存位置),而不是实际的原始值。由于变量本身只是指向 PyObject*
指针的字典中的条目,因此更改变量的值实际上是给它一个不同的内存地址以指向它。
在CPython中,正是这些PyObject*
值被id
和is
使用(a is b
与id(a) == id(b)
.)
例如,让我们考虑简单的代码行:
# x: int
x += 1
实际上改变了与变量关联的内存位置。这是因为它遵循以下逻辑:
LOAD_FAST (x)
LOAD_CONST (1)
INPLACE_ADD
STORE_FAST (x)
哪个字节码粗略地说:
查找x的值。这是一个(在 CPython 中)PyObject*
,它指向 PyLongLong
或类似的(来自 Python 用户区的 int
)
从常量内存地址加载一个值
- 将这两个值相加。这将产生一个新的
PyObject*
,它也是一个 int
- 将与
x
关联的值设为这个新指针
TL;DR:Python 中的一切,包括原语,都是对象。变量本身不存储值,而是存储它们的指针。重新分配变量会更改与该名称关联的指针,而不是更新该位置的内存。
"假设我有一个对象存储在变量 a" - 那就是你出错的地方。
Python 对象不存储 在 变量中,它们被变量 引用 。
a = [1, 2, 3]
b = a
a
和 b
指的是同一个对象。 list
对象的 引用计数 为 2,因为有两个名称引用它。
a = {'x': 'y'}
a
不再引用同一个 list
对象,而是引用 dict
对象。这会减少 list
对象的引用计数,但 b
仍然引用它,因此对象的引用计数现在为 1。
b = None
这意味着 b
现在引用 None
对象(它具有非常高的引用计数,很多名称都引用 None
)。 list
对象的引用计数再次减少并降为零。此时 list
对象可以被垃圾收集并释放内存(不保证何时发生)。
Python 有 names 引用 objects。对象与名称分开存在,名称与它们所引用的对象分开存在。
# name a
a = 1337
# object 1337
当分配"a name to a name"时,right-hand端被计算到被引用的对象。类似于 2 + 2
计算为 4
,a
计算为原始 1337
。
# name b
b = a
# object referred to by a -> 1337
此时,我们有 a -> 1337
和 b -> 1337
- 请注意,这两个名字互不相识!如果我们测试 a is b
,两个名称都被 评估 到显然相等的同一个对象。
重新分配名称只会更改该名称所指的内容 - 其他名称也无法更改。
# name a - reassign
a = 9001
# object 9001
此时,我们有 a -> 9001
和 b -> 1337
。如果我们现在测试 a is b
,两个名称都会 评估 到不同的对象。
如果您来自 C 等语言,那么您会习惯 包含 值的变量。例如,char a = 12
可以读作“a
是一个包含 12
的内存区域”。最重要的是,您可以让多个变量使用相同的内存。为变量分配另一个值会更改共享内存的内容 - 从而更改两个变量的值。
+- char a -+
| 12 |
+--char b -+
# a = -128
+- char a -+
| -128 |
+--char b -+
这不是 Python 的工作方式:名称不包含任何内容,而是引用单独的值。例如,a = 12
可以读作“a
是一个引用值 12
的名称”。最重要的是,您可以让多个名称引用相同的值——但它们仍然是不同的名称,每个名称都有自己的引用。为名称分配另一个值会更改该名称的引用 - 但不会影响其他名称的引用。
+- name a -+ -\
\
--> +- <12> ---+
/ | 12 |
+- name b -+ -/ +----------+
# a = -128
+- <-128> -+
+- name a -+ -----> | -128 |
+----------+
+- <12> ---+
+- name b -+ -----> | 12 |
+----------+
一个令人困惑的地方是 mutable 对象似乎违反了名称和对象的分离。通常,这些容器(例如 list
、dict
、...)和 类 默认情况下表现出相同的行为。
# name m
m = [1337]
# object [1337]
# name n
n = m
# object referred to by m
与普通整数 1337
类似,包含整数 [1337]
的列表是 一个对象 ,可以由多个独立名称引用。如上,n is m
计算为 True
而 m = [9001]
不会改变 n
.
但是,对名称的某些操作会更改名称 和所有别名 .
看到的值
# inplace add to m
m += [9001]
这样操作后,m == [1337, 9001]
和n is m
仍然成立。其实n
看到的值也变成了[1337, 9001]
。这似乎违反了上述行为,其中别名不会相互影响。
这是因为 m += [9001]
没有改变 m
所指的内容。它仅更改 m
(和别名 n
)引用的列表的 content。 m
和 n
仍然引用原始列表对象,其 value 已更改。
+- name m -+ -\
\
--> +- […] -+ +--- <@0> -+
/ | @0 | -> | 1337 |
+- name n -+ -/ +-------+ +----------+
# m += [9001]
+- name m -+ -\
\
--> +- […] -+ +--- <@0> -++--- <@1> -+
/ | @0 @1 | -> | 1337 || 9001 |
+- name n -+ -/ +-------+ +----------++----------+
我用通俗易懂的语言给你解释,让你看得懂
案例一
a = [1, 2, 3]
b = a
print(b is a)
a
的值为[1,2,3]
。现在我们也通过 a
将 [1,2,3]
分配给 b
。所以两者具有相同的值,因此 b is a
= True
.
下一步,
a = {'x': 'y'}
print(a is b)
现在您正在将 a
的值更改为 {'x':'y'}
但是 我们的 b
仍然与 [1,2,3]
相同。所以现在 a is b
是 False
。
Case-2 如果您已完成以下操作:-
a = [1, 2, 3]
b = a
print(b is a)
a = {'x': 'y'}
b = a # Reassigning the value of b.
print(a is b)
重新分配a
的值后,我也在重新分配b
的值。
因此,在这两种情况下你都会得到 True
。
希望对您有所帮助。
我正在尝试了解变量在 python 中的工作原理。假设我有一个对象存储在变量 a
:
>>> a = [1, 2, 3]
如果我将 a
分配给 b
,两者都指向同一个对象:
>>> b = a
>>> b is a
True
但如果我重新分配 a
或 b
,那将不再正确:
>>> a = {'x': 'y'}
>>> a is b
False
这两个变量现在有不同的值:
>>> a
{'x': 'y'}
>>> b
[1, 2, 3]
我不明白为什么变量现在不同了。为什么 a is b
不再正确?谁能解释一下这是怎么回事?
在Python中,所有变量都存储在字典中,或者是看起来很像字典的结构(例如locals()
可以将当前的scope/namespace公开为字典)。
注意:PyObject*
是一个CPython概念。我不确定在其他 Python 实现中是如何工作的。
因此,查看 Python 变量如 C 的变量具有精确的内存位置是有缺陷的。它们的值是PyObject*
(指针或内存位置),而不是实际的原始值。由于变量本身只是指向 PyObject*
指针的字典中的条目,因此更改变量的值实际上是给它一个不同的内存地址以指向它。
在CPython中,正是这些PyObject*
值被id
和is
使用(a is b
与id(a) == id(b)
.)
例如,让我们考虑简单的代码行:
# x: int
x += 1
实际上改变了与变量关联的内存位置。这是因为它遵循以下逻辑:
LOAD_FAST (x)
LOAD_CONST (1)
INPLACE_ADD
STORE_FAST (x)
哪个字节码粗略地说:
查找x的值。这是一个(在 CPython 中)
PyObject*
,它指向PyLongLong
或类似的(来自 Python 用户区的int
)从常量内存地址加载一个值
- 将这两个值相加。这将产生一个新的
PyObject*
,它也是一个int
- 将与
x
关联的值设为这个新指针
TL;DR:Python 中的一切,包括原语,都是对象。变量本身不存储值,而是存储它们的指针。重新分配变量会更改与该名称关联的指针,而不是更新该位置的内存。
"假设我有一个对象存储在变量 a" - 那就是你出错的地方。
Python 对象不存储 在 变量中,它们被变量 引用 。
a = [1, 2, 3]
b = a
a
和 b
指的是同一个对象。 list
对象的 引用计数 为 2,因为有两个名称引用它。
a = {'x': 'y'}
a
不再引用同一个 list
对象,而是引用 dict
对象。这会减少 list
对象的引用计数,但 b
仍然引用它,因此对象的引用计数现在为 1。
b = None
这意味着 b
现在引用 None
对象(它具有非常高的引用计数,很多名称都引用 None
)。 list
对象的引用计数再次减少并降为零。此时 list
对象可以被垃圾收集并释放内存(不保证何时发生)。
Python 有 names 引用 objects。对象与名称分开存在,名称与它们所引用的对象分开存在。
# name a
a = 1337
# object 1337
当分配"a name to a name"时,right-hand端被计算到被引用的对象。类似于 2 + 2
计算为 4
,a
计算为原始 1337
。
# name b
b = a
# object referred to by a -> 1337
此时,我们有 a -> 1337
和 b -> 1337
- 请注意,这两个名字互不相识!如果我们测试 a is b
,两个名称都被 评估 到显然相等的同一个对象。
重新分配名称只会更改该名称所指的内容 - 其他名称也无法更改。
# name a - reassign
a = 9001
# object 9001
此时,我们有 a -> 9001
和 b -> 1337
。如果我们现在测试 a is b
,两个名称都会 评估 到不同的对象。
如果您来自 C 等语言,那么您会习惯 包含 值的变量。例如,char a = 12
可以读作“a
是一个包含 12
的内存区域”。最重要的是,您可以让多个变量使用相同的内存。为变量分配另一个值会更改共享内存的内容 - 从而更改两个变量的值。
+- char a -+
| 12 |
+--char b -+
# a = -128
+- char a -+
| -128 |
+--char b -+
这不是 Python 的工作方式:名称不包含任何内容,而是引用单独的值。例如,a = 12
可以读作“a
是一个引用值 12
的名称”。最重要的是,您可以让多个名称引用相同的值——但它们仍然是不同的名称,每个名称都有自己的引用。为名称分配另一个值会更改该名称的引用 - 但不会影响其他名称的引用。
+- name a -+ -\
\
--> +- <12> ---+
/ | 12 |
+- name b -+ -/ +----------+
# a = -128
+- <-128> -+
+- name a -+ -----> | -128 |
+----------+
+- <12> ---+
+- name b -+ -----> | 12 |
+----------+
一个令人困惑的地方是 mutable 对象似乎违反了名称和对象的分离。通常,这些容器(例如 list
、dict
、...)和 类 默认情况下表现出相同的行为。
# name m
m = [1337]
# object [1337]
# name n
n = m
# object referred to by m
与普通整数 1337
类似,包含整数 [1337]
的列表是 一个对象 ,可以由多个独立名称引用。如上,n is m
计算为 True
而 m = [9001]
不会改变 n
.
但是,对名称的某些操作会更改名称 和所有别名 .
看到的值# inplace add to m
m += [9001]
这样操作后,m == [1337, 9001]
和n is m
仍然成立。其实n
看到的值也变成了[1337, 9001]
。这似乎违反了上述行为,其中别名不会相互影响。
这是因为 m += [9001]
没有改变 m
所指的内容。它仅更改 m
(和别名 n
)引用的列表的 content。 m
和 n
仍然引用原始列表对象,其 value 已更改。
+- name m -+ -\
\
--> +- […] -+ +--- <@0> -+
/ | @0 | -> | 1337 |
+- name n -+ -/ +-------+ +----------+
# m += [9001]
+- name m -+ -\
\
--> +- […] -+ +--- <@0> -++--- <@1> -+
/ | @0 @1 | -> | 1337 || 9001 |
+- name n -+ -/ +-------+ +----------++----------+
我用通俗易懂的语言给你解释,让你看得懂
案例一
a = [1, 2, 3]
b = a
print(b is a)
a
的值为[1,2,3]
。现在我们也通过 a
将 [1,2,3]
分配给 b
。所以两者具有相同的值,因此 b is a
= True
.
下一步,
a = {'x': 'y'}
print(a is b)
现在您正在将 a
的值更改为 {'x':'y'}
但是 我们的 b
仍然与 [1,2,3]
相同。所以现在 a is b
是 False
。
Case-2 如果您已完成以下操作:-
a = [1, 2, 3]
b = a
print(b is a)
a = {'x': 'y'}
b = a # Reassigning the value of b.
print(a is b)
重新分配a
的值后,我也在重新分配b
的值。
因此,在这两种情况下你都会得到 True
。
希望对您有所帮助。