Python中的可变与不可变对象

Python中的可变与不可变对象

Python中的所有东西都是一个对象。每个Python新手都应该学习的是,Python中的所有对象都可以是可变的或不可变的。
这里写图片描述
让我们更深入地了解它的细节…因为Python中的所有东西都是对象,所以每个变量都包含一个对象实例。当一个对象被初始化时,它被分配一个唯一的对象ID。它的类型是在运行时定义的,一旦设置永远不会改变,但是如果它是可变类型它的状态是可以被改变的。简单地说,一个可变对象可以在创建之后被改变,一个不可变对象不能。

诸如(int,float,bool,str,tuple,unicode)等内置类型的对象是不可变的。像(list,set,dict)这样的内置类型的对象是可变的。自定义类通常是可变的。要模拟类中的不变性,应该覆盖属性设置和删除以引发异常。
这里写图片描述
现在出现这个问题,我们如何确定我们的变量是一个可变对象还是不可变对象。为此,我们应该了解“ID”和“TYPE”函数的用途。

ID和TYPE

内置函数id()以整数形式返回对象的标识。这个整数通常对应于对象在内存中的位置,虽然这是针对Python的实现和正在使用的平台而言。is运算符用来比较两个对象的标识。

内置函数type()返回一个对象的类型。让我们看一个简单的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
'''示例1''' 
>>> x =“Holberton”
>>> y =“Holberton”
>>> id(x)
140135852055856
>>> id(y)
140135852055856
>>> print(x is y)' ''比较类型'''
真的
'''示例2'''
>>> a = 50
>>> type(a)
<class:'int'>
>>> b =“Holberton”
>>> type(b)
<class:'string'>

现在我们已经看到了如何比较两个简单的字符串变量来找出类型和ID。因此,使用这两个函数,我们可以检查不同类型的对象是如何与变量相关联以及如何更改对象的。

可变和不可变的对象

正如我们前面所讨论的,一个可变对象可以改变它的状态或内容,不可变对象不能。

  • 可变对象
    列表,字典,集,字节数组

  • 不可变对象
    int,float,complex,string,tuple,frozen set [注:set的不可变版本],bytes

一个实用的例子来找出对象类型的可变性

1
2
x = 10
x = y

我们正在创建一个int类型的对象。标识符x和y指向同一个对象。

1
2
id(x)== id(y)
id(y)== id(10

如果我们做一个简单的操作。

1
x = x + 1

现在

1
2
id(x)!= id(y)
id(x)!= id(10

x被标记的对象被改变。对象10从未被修改过。不可变对象在创建后不允许修改

在可变对象的情况下

1
2
m = list([123])
n = m

我们正在创建一个类型列表的对象。标识符m和n被标记为同一个列表对象,它是3个不可变的int对象的集合。

1
id(m)== id(n)

现在从列表对象中弹出一个元素确实会改变对象,

1
m.pop()

对象ID不会被更改

1
id(m)== id(n)

m和n将在修改后指向同一个列表对象。列表对象现在将包含[1,2]。

那么从上面的例子中我们看到了什么?

Python以不同方式处理可变对象和不可变对象。
不可变的访问比可变对象更快。
当你需要改变对象的大小,例如列表,字典等等时,可变对象是很好用的。当你需要确保你创建的对象始终保持不变时,使用不可变对象。
不可变对象对于“更改”而言基本上是昂贵的,因为这样做涉及到创建副本。更改可变对象很便宜。

不可变的例外..

并非所有的不可变对象都是不可变的。

如前所述,Python容器比如元组,是不可变的。这意味着一个tuple的值在创建后无法更改。但是元组的“值”实际上是一系列名称,它们与对象的绑定是不可改变的。关键点是要注意绑定是不可改变的,而不是它们绑定的对象。

让我们考虑一个元组t =(’holberton’,[1,2,3])

上面的元组t包含不同数据类型的元素,第一个元素是一个不可变的字符串,第二个元素是一个可变列表。元组本身不可变。即它没有任何改变其内容的方法。同样,字符串是不可变的,因为字符串没有任何可变方法。但是列表对象确实有可变方法,所以可以改变它。这是一个微妙的点,但是非常重要:不可变对象的“值” 不能改变,但它的组成对象是能做到改变的。

对象如何传递给函数

对于我们来说,了解可变类型和不可变类型之间的区别以及将它们传递到函数时如何处理是非常重要的。当使用正确的对象时,存储效率会受到很大影响。

例如,如果一个可变对象在函数中被引用调用,它可以改变原来的变量本身。因此为了避免这种情况,需要将原始变量复制到另一个变量中。不可变的对象可以通过引用来调用,因为它的值不能被改变。

1
2
3
4
5
6
7
def updateListlist1):
list1 + = [10]
n = [56]
print(id(n))#140312184155336
updateList(n)
print(n)#[
5,6,10 ] print(id(n))#140312184155336

从上面的例子我们可以看到,我们通过引用调用了列表,因此对原始列表本身进行了更改。

让我们看看另一个例子:

1
2
3
4
5
6
7
def updateNumbern):
printidn))
n + = 10
b = 5
print(id(b))#10055680
updateNumber(b)#10055680
print(b)#5

在上面的例子中,同一个对象被传递给函数,但即使对象是相同的,变量值也不会改变。这被称为按价值传递 。那么到底发生了什么?当函数调用该值时,只传递该变量的值,而不传递该对象本身。所以引用该对象的变量不会改变,但对象本身正在改变,但仅在函数范围内。因此,这种变化没有体现出来。