Python字符串不是一成不变的吗?那么为什么a +“” + b有效呢?


109

我的理解是Python字符串是不可变的。

我尝试了以下代码:

a = "Dog"
b = "eats"
c = "treats"

print a, b, c
# Dog eats treats

print a + " " + b + " " + c
# Dog eats treats

print a
# Dog

a = a + " " + b + " " + c
print a
# Dog eats treats
# !!!

Python是否应该阻止分配?我可能错过了一些东西。

任何想法?


55
字符串本身是不可变的,但标签可以更改。
米奇2012年

6
将新值分配给现有变量是完全有效的。Python没有常数。这与数据类型的可变性无关。
Felix Kling'2

14
您可能需要看一下该id()功能。a分配前后的ID将不同,表明它指向的对象不同。同样,对于类似的代码,b = a您会发现,a并且b将具有相同的ID,表示它们引用的是同一对象。
DRH'2


来自delnan的链接正是我所指的。
米奇2012年

Answers:


180

首先a指向字符串“ Dog”。然后,您将变量更改a为指向新的字符串“ Dog eats对待”。您实际上并未更改字符串“ Dog”。字符串是不可变的,变量可以指向它们想要的任何东西。


34
尝试说x ='abc'更令人信服;Python代表中的x [1] ='
x'– xpmatteo

1
如果您想进一步了解内部原理,请参阅我的答案。stackoverflow.com/a/40702094/117471
Bruno Bronosky '18年

53

字符串对象本身是不可变的。

a指向字符串的变量是可变的。

考虑:

a = "Foo"
# a now points to "Foo"
b = a
# b points to the same "Foo" that a points to
a = a + a
# a points to the new string "FooFoo", but b still points to the old "Foo"

print a
print b
# Outputs:

# FooFoo
# Foo

# Observe that b hasn't changed, even though a has.

@jason尝试使用与列表相同的操作(可变的)来查看区别a.append(3)对应于a = a +“ Foo”
jimifiki 2012年

1
@jimifiki a.append(3) 是不一样的a = a + 3。它甚至都不是a += 3(就地加法等效于.extend,而不是.append)。

@delnan等什么?为了显示字符串和列表的行为不同,您可以假定a = a +“ Foo”与a.append(something)相同。无论如何都不一样。明显。您是不是更喜欢阅读a.extend([something])而不是a.append(something)?在这种情况下,我认为没有太大的区别。但可能我缺少一些东西。阈值取决于上下文
jimifiki'2

@jimifiki:您在说什么?+列表和字符串的行为相同-通过创建新副本并不改变任何一个操作数来串联。

6
摆脱所有这些的真正重要的一点是,字符串没有 append 功能,因为它们是不可变的。
莉莉·钟 Lily Chung)

46

变量a指向对象“ Dog”。最好将Python中的变量视为标签。您可以将标签移动到其他对象,这就是您更改为时的a = "dog"操作a = "dog eats treats"

但是,不变性是指对象,而不是标签。


如果你试图a[1] = 'z'"dog""dzg",你会得到错误:

TypeError: 'str' object does not support item assignment" 

因为字符串不支持项目分配,所以它们是不可变的。


19

只有当我们能够更改存储位置中保存的值而不更改存储位置本身时,某些事情才是可变的。

诀窍是:如果发现更改前后的内存位置相同,则它是可变的。

例如,列表是可变的。怎么样?

>> a = ['hello']
>> id(a)
139767295067632

# Now let's modify
#1
>> a[0] = "hello new"
>> a
['hello new']
Now that we have changed "a", let's see the location of a
>> id(a)
139767295067632
so it is the same as before. So we mutated a. So list is mutable.

字符串是不可变的。我们如何证明呢?

> a = "hello"
> a[0]
'h'
# Now let's modify it
> a[0] = 'n'
----------------------------------------------------------------------

我们得到

TypeError:'str'对象不支持项目分配

因此,我们无法使字符串发生变异。这意味着字符串是不可变的。

在重新分配中,您将变量更改为指向新位置本身。在这里,您没有改变字符串,而是改变了变量本身。以下是您正在做什么。

>> a = "hello"
>> id(a)
139767308749440
>> a ="world"
>> id(a)
139767293625808

id重新分配之前和之后是不同的,因此这证明您实际上不是在变异,而是将变量指向新位置。这不是改变字符串,而是改变变量。


11

变量只是指向对象的标签。该对象是不可变的,但是如果需要,可以使标签指向完全不同的对象。


8

考虑:

>>> a='asdf'
>>> a.__repr__
<method-wrapper '__repr__' of str object at 0x1091aab90>
>>> a='asdf'
>>> a.__repr__
<method-wrapper '__repr__' of str object at 0x1091aab90>
>>> a='qwer'
>>> a.__repr__
<method-wrapper '__repr__' of str object at 0x109198490>

请注意,当我在变量中两次存储相同的值时,十六进制存储位置没有改变。当我存储其他值时,它确实发生了变化。该字符串是不可变的。不是因为狂热,而是因为您付出了在内存中创建新对象的性能损失。该变量a只是指向该内存地址的标签。可以更改它以指向任何内容。


7

该语句a = a + " " + b + " " + c可以基于指针进行分解。

a + " "说给我a指向什么,不能更改,然后添加" "到我当前的工作集中。

记忆:

working_set = "Dog "
a = "Dog" 
b = "eats"
c = "treats"

+ b告诉我要b指向的内容,不能更改的内容,然后将其添加到当前工作集中。

记忆:

working_set = "Dog eats"
a = "Dog" 
b = "eats"
c = "treats"

+ " " + c说添加" "到当前集。然后给我c指出什么,不能更改,然后将其添加到当前工作集中。记忆:

working_set = "Dog eats treats"
a = "Dog" 
b = "eats"
c = "treats"

最后,a =说出将我的指针指向结果集。

记忆:

a = "Dog eats treats"
b = "eats"
c = "treats"

"Dog"被回收,因为没有更多的指针连接到它的内存块。我们从未修改过"Dog"驻留的内存部分,这就是不可变的意思。但是,我们可以更改指向该内存部分的标签(如果有)。


6
l = [1,2,3]
print id(l)
l.append(4)
print id(l) #object l is the same

a = "dog"
print id(a)
a = "cat"
print id(a) #object a is a new object, previous one is deleted

5

数据与其关联的标签之间存在差异。例如当你做

a = "dog"

数据"dog"被创建并放置在标签下a。标签可以更改,但内存中的内容不会更改。执行"dog"完该操作后,数据仍将存在于内存中(直到垃圾回收器将其删除)

a = "cat"

a现在在您的程序中,^指向^,"cat"但是字符串"dog"没有改变。


3

Python字符串是不可变的。但是,a它不是字符串:它是具有字符串值的变量。您不能更改字符串,但是可以将变量的值更改为新字符串。


2

变量可以指向它们想要的任何位置。如果执行以下操作,将引发错误:

a = "dog"
print a                   #dog
a[1] = "g"                #ERROR!!!!!! STRINGS ARE IMMUTABLE

2

Python字符串对象是不可变的。例:

>>> a = 'tanim'
>>> 'Address of a is:{}'.format(id(a))
'Address of a is:64281536'
>>> a = 'ahmed'
>>> 'Address of a is:{}'.format(id(a))
'Address of a is:64281600'

在这个例子中,我们可以看到当我们在a中分配不同的值时,它不会被修改,而是创建了一个新对象。
而且无法修改。例:

  >>> a[0] = 'c'
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    **TypeError**: 'str' object does not support item assignment

发生错误。


2

'mutable'意味着我们可以更改字符串的内容,'immutable'意味着我们不能添加额外的字符串。

点击查看照片证明


1

>>> a = 'dogs'

>>> a.replace('dogs', 'dogs eat treats')

'dogs eat treats'

>>> print a

'dogs'

一成不变,不是吗?

变量更改部分已经讨论过。


1
这并不能证明或反驳python字符串的可变性,只是该replace()方法返回了一个新字符串。
布伦特·赫罗尼克

1

考虑这个示例

 a = "Dog"
 b = "eats"
 c = "treats"
 print (a,b,c)
 #Dog eats treats
 d = a + " " + b + " " + c
 print (a)
 #Dog
 print (d)
 #Dog eats treats

我在博客中找到的更精确的解释之一是:

在Python中,(几乎)所有事物都是一个对象。在Python中我们通常称为“变量”的名称更恰当。同样,“赋值”实际上是名称与对象的绑定。每个绑定都有一个定义其可见性的范围,通常是名称起源的块。

例如:

some_guy = 'Fred'
# ...
some_guy = 'George'

当我们稍后说some_guy ='George'时,包含'Fred'的字符串对象不受影响。我们刚刚更改了名称some_guy的绑定。但是,我们没有更改'Fred'或'George'字符串对象。就我们而言,他们可能会无限期地生活下去。

链接到博客:https : //jeffknupp.com/blog/2012/11/13/is-python-callbyvalue-or-callbyreference-nether/


1

在上述答案中添加更多内容。

id 变量的变化在重新分配时发生变化。

>>> a = 'initial_string'
>>> id(a)
139982120425648
>>> a = 'new_string'
>>> id(a)
139982120425776

这意味着我们已经将变量突变a为指向新的字符串。现在有两个 string(str)对象:

'initial_string'id= 139982120425648

'new_string'id= 139982120425776

考虑下面的代码:

>>> b = 'intitial_string'
>>> id(b)
139982120425648

现在,b指向'initial_string',并具有相同ida重新分配过的。

因此,'intial_string'尚未被突变。


0

总结:

a = 3
b = a
a = 3+2
print b
# 5

不是一成不变的:

a = 'OOP'
b = a
a = 'p'+a
print b
# OOP

不可变:

a = [1,2,3]
b = range(len(a))
for i in range(len(a)):
    b[i] = a[i]+1

这是Python 3中的错误,因为它是不可变的。在Python 2中也不是错误,因为显然它不是不可变的。


0

内置函数id()以整数形式返回对象的标识。该整数通常对应于对象在内存中的位置。

\>>a='dog'
\>>print(id(a))

139831803293008

\>>a=a+'cat'
\>>print(id(a))

139831803293120

最初,“ a”存储在139831803293008的存储位置中,因为如果您尝试修改和重新分配该字符串对象,则在python中该对象是不可变的,该引用将被删除,并将成为新存储位置的指针(139831803293120)。


0
a = 'dog'
address = id(a)
print(id(a))

a = a + 'cat'
print(id(a))      #Address changes

import ctypes
ctypes.cast(address, ctypes.py_object).value    #value at old address is intact

2
尽管此代码可以解决OP的问题,但最好包含有关您的代码如何解决OP的问题的说明。这样,将来的访问者可以从您的帖子中学习,并将其应用于自己的代码。SO不是编码服务,而是知识资源。此外,更可能会推荐高质量,完整的答案。这些功能以及所有职位必须自成体系的要求,是SO作为平台的强项,可以将其与论坛区分开。您可以编辑以添加其他信息和/或在源文档中补充说明
SherylHohman


-1

我们只是串联两个字符串值。我们永远不会改变(a)的值。刚才(a)表示另一个具有“ dogdog”值的存储块。因为在后端,一个变量永远不会代表两个内存块。串联前(a)的值为“ dog”。但是在那之后(a)代表“ dogdog”,因为现在(a)在后端代表中。具有“ dogdog”值的块。而“狗”是代表。直到(b)代表“狗”,“ b”和“ dog”才算作垃圾值。

困惑在于我们用相同的变量名来表示后端的内存块(包含数据或信息)。


-2

您可以使numpy数组不变,并使用第一个元素:

numpyarrayname[0] = "write once"

然后:

numpyarrayname.setflags(write=False)

要么

numpyarrayname.flags.writeable = False
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.