Python中的可变和不可变

Python中的可变和不可变,python,immutability,mutable,Python,Immutability,Mutable,我是Python新手,试图理解可变对象和不可变对象之间的区别。Python中的可变类型之一是list。假设L=[1,2,3],那么L有一个指向对象[1,2,3]的id。如果修改了[1,2,3]的内容,则L仍保留相同的id。换句话说,即使对象的大小和内容已更改,L仍与同一对象关联 对于不可变对象,我的理解是不允许修改对象。因此,如果用新值重新分配变量,则该变量将绑定到具有不同id的新对象。但是我试图修改一个字符串,但是字符串id没有改变 string = "blue" for i in range

我是Python新手,试图理解可变对象和不可变对象之间的区别。Python中的可变类型之一是list。假设L=[1,2,3],那么L有一个指向对象[1,2,3]的id。如果修改了[1,2,3]的内容,则L仍保留相同的id。换句话说,即使对象的大小和内容已更改,L仍与同一对象关联

对于不可变对象,我的理解是不允许修改对象。因此,如果用新值重新分配变量,则该变量将绑定到具有不同id的新对象。但是我试图修改一个字符串,但是字符串id没有改变

string = "blue"
for i in range(10):
    string = string + str(i)
    print("string id after {}th iteration: {}".format(i,id(string)))


string id after 0th iteration: 46958272
string id after 1th iteration: 46958272
string id after 2th iteration: 46958272
string id after 3th iteration: 47077400
string id after 4th iteration: 47077400
string id after 5th iteration: 47077400
string id after 6th iteration: 47077400
string id after 7th iteration: 47077400
string id after 8th iteration: 47077400
string id after 9th iteration: 47077400

您确实不应该在一行中看到同一个ID两次,但是CPython使用
+
对字符串连接进行了优化,它并没有完全遵守它应该遵守的所有规则

当CPython看到形式为
x=x+something
x+=something
的操作时,如果
x
引用一个字符串,并且
x
持有对该字符串的唯一引用,那么CPython将使用
realloc
来增长该字符串,而不是创建一个新的字符串对象。根据可用内存的详细信息,
realloc
可以调整已分配内存的大小,也可以分配新内存。如果调整分配的大小,对象的
id
将保持不变。您可以在
Python/ceval.c
中看到实现

这种优化基本上是好的,因为refcount检查确保它的行为基本上就像字符串真的不可变并且创建了一个新字符串一样。但是,在
x=x+stuff
中,旧字符串和新字符串的生存期应该短暂重叠,因为新字符串应该在赋值结束旧字符串的生存期之前存在,所以ID值不可能相等


id
是优化明显不同于未发生字符串变异的几种方式之一。语言开发人员似乎认为他们对此没有意见。

如果以空字符串开始,那么重复的可能副本根本无法回答有关字符串的
id
s的问题;
id
即使在第6次迭代之前仍然保持不变…@hiroprotation,但随后它会发生变化并不断变化。旁白:
string
是一个糟糕的变量名,因为它与
string
模块名冲突。请提供一个参考,说明该语言保证“生存期”对象的生命周期应该持续到赋值完成,而不是在语句级别定义,以便在语句执行期间随时丢弃。@jpmc26:Python文档没有定义术语“生命周期”,尽管它使用了该术语。但是,没有合理的方法来定义它的语句级,最接近合理的语句级定义仍然会禁止相等的ID值(因为新旧字符串在同一语句中是活动的)(特别是对于多线程程序,无需假设GIL来线性化),很难对同时存在的两个对象被视为具有不重叠的生存期的任何解释提出异议,如果没有优化,新旧字符串肯定会同时存在。赋值的RHS必须在命名(re)之前进行充分评估发生绑定。如果您关心的是该语言是否保证不会提前放弃赋值的LHS,请参阅,其中说明只有在新对象可用时才会发生名称(重新)绑定。字符串连接优化通过提前解除名称绑定来作弊。