可变对象和不可变对象

在python中一切皆对象。在Python中不存在所谓的值传递调用,一切传递都是对象的引用,也可认为是传址。

python中,对象分为可变(mutable)和不可变(immutable)两种类型,元组(tuple)、数值型(number)、字符串(string)均为不可变对象,而字典型(dictionary)和列表型(list)的对象是可变对象。

不可变对象

见一个例子,分析不可变对象的特点

python内置id()函数,用于返回对象的唯一标识(identity)。id()返回的是十进制,对象实际内存地址为hex(id(object)),本文中将id()与hex(id())等价使用。

>>> a = 1 #将变量a与内存中的值为1的内存绑定在一起
>>> a = 2 # 将变量a与内存中值为2的内存绑定在一起,并不是修改原来a绑定的内存中的值,
# 这时,原来的这个值为1的内存地址的引用次数减一,当引用计数为0时,内存地址被回收
>>> b = a # 变量b绑定与a一样的内存
>>> id(b),id(a) # 打印a,b的绑定的内存地址
(1972461824, 1972461824)
>>> b = 3 # 创建一个内存值为3的内存地址与变量名字b进行绑定。这时,a还是指向值为2的内存地址
>>> a,b
(2, 3)
>>> id(b),id(a) # 打印a,b的绑定的内存地址
(1972461856, 1972461824)
>>> x = 1
>>> y = 1
>>> z = 1
>>> x is y
True
>>> y is z
True
>>> id(x),id(y),id(z)
(1972461792, 1972461792, 1972461792)

从第二个例子可看出因为整数为不可变对象,x,y,z在内存中均指向一个值为1的内存地址。
不可变对象最大的优点便是减少重复的值对内存空间的占用。

缺点便是如第一个例子中所示,我要修改这个变量绑定的值,如果内存中没有存在该值的内存块,那么必须重新开辟一块内存,把新地址与变量名绑定。

而不是修改变量原来指向的内存块的值,这回给执行效率带来一定的降低。

原来的内存块会因变量与其他内存块绑定而引用次数减1.

下面是对第一个例子的图解

可变对象

继续看一个例子

例1

>>> a = [1]
>>> b = a # a,b绑定同一内存地址,内存中值为[1]
>>> id(a),id(b)
(1991064334856, 1991064334856)
>>> b.append(2)
>>> a,b
([1, 2], [1, 2])

变量名a和b是绑定的同一内存地址,对任一个变量对应的值的修改,都会反映到另一个变量上。也就是说对可变对象的操作,是直接对这个对象进行改变。

例2

>>> a =[1]
>>> b=[1]
>>> id(a),id(b)
(1991065258248, 1991064760520)

由此可见,在可变对象中值相同的变量绑定的不一定是相同的内存地址,会指向不同的对象。

例3

在函数默认形参值中使用可变对象会出现一个大坑见例子:

>>> def add_end(L=[]):
... L.append('End')
... return L
...
>>> add_end([1,2])
[1, 2, 'End']
>>> add_end([a,b])
[[1], [1], 'End']
>>> add_end()
['End']
>>> add_end()
['End', 'End']
>>> add_end()
['End', 'End', 'End']

这里当正常传参调用函数时一切正常,可是不断调用带默认形参是值得函数时,我们觉得的答案应该是['End'],但是仿佛次调用都记住了上次的值。

这原因为何?

这看起来有点像是每次调用的默认形参值变化了。我们用add_end.__defaults__来查看函数对象的默认参数变化情况

>>> def add_end(L=[]):
... L.append('End')
... return L
...
>>> add_end.__defaults__
([],)
>>> add_end()
['End']
>>> add_end.__defaults__
(['End'],)
>>> add_end()
['End', 'End']
>>> add_end()
['End', 'End', 'End']
>>> add_end.__defaults__
(['End', 'End', 'End'],)

由上可知每调用一次带默认形参值的函数,该函数对象的默认形参值就会发生变化,所以下次调用的默认形参值就是变化过后的。究其原因还是因为默认形参值为可变对象,导致每次调用会改变默认形参值

所以,在编程时可尽量将对象设计为不变对象,可以避免一些麻烦。

赋值操作技巧---序列解包(递归解包)

将含多个值的序列解开,放到变量的序列中。解包序列中元素的数量必须和赋值符号=左边变量数量一致,否则会报错,参数太多或太少。

>>> values = 1,2,3
>>> values
(1, 2, 3)
>>> x,y,z = values
>>> print(x,y,z)
1 2 3
>>> a,b,c = 1,2,3,4,5
Traceback (most recent call last):
File "<input>", line 1, in <module>
ValueError: too many values to unpack (expected 3)
>>> a,b,c=1,2
Traceback (most recent call last):
File "<input>", line 1, in <module>
ValueError: not enough values to unpack (expected 3, got 2)

当解包序列中的元素多多余变量数量时,变量序列中可以使用星号运算符*,对值进行收集

>>> a,b,*c=1,2,3,4,5
>>> print(a,b,c)
1 2 [3, 4, 5]

Python中的可变、不可变对象和赋值技巧序列解包的更多相关文章

  1. Python中为什么不能用可变对象作为默认参数的值

    def func(numbers = [], num=1): numbers.append(num) for number in numbers: print(number) func() >& ...

  2. Python中使用operator模块实现对象的多级排序

    Python中使用operator模块实现对象的多级排序 今天碰到一个小的排序问题,需要按嵌套对象的多个属性来排序,于是发现了Python里的operator模块和sorted函数组合可以实现这个功能 ...

  3. 在python中元组与列表的区别及序列解包

    一. 元组与列表的区别 元组中的数据一旦定义就不允许更改. 元组没有append().extend()和insert()等方法,无法向元组中添加元素. 元组没有remove()或pop()方法,也无法 ...

  4. Python中序列解包与函数的参数收集之间的关系

    在<第4.7节 Python特色的序列解包.链式赋值.链式比较>中老猿介绍了序列解包,<第5.2节 Python中带星号的函数参数实现参数收集>介绍了函数的参数收集,实际上函数 ...

  5. Python特色的序列解包、链式赋值、链式比较

    一.序列解包 序列解包(或可迭代对象解包):解包就是从序列中取出其中的元素的过程,将一个序列(或任何可迭代对象)解包,并将得到的值存储到一系列变量中. 一般情况下要解包的序列包含的元素个数必须与你在等 ...

  6. 第4.7节 Python特色的序列解包、链式赋值、链式比较

    一.序列解包 序列解包(或可迭代对象解包):解包就是从序列中取出其中的元素的过程,将一个序列(或任何可迭代对象)解包,并将得到的值存储到一系列变量中. 一般情况下要解包的序列包含的元素个数必须与你在等 ...

  7. python 零散记录(五) import的几种方式 序列解包 条件和循环 强调getattr内建函数

    用import关键字导入模块的几种方式: #python是自解释的,不必多说,代码本身就是人可读的 import xxx from xxx import xxx from xxx import xx1 ...

  8. Python——序列封包与序列解包

    一.序列封包与序列解包 把多个值赋给一个变量时,Python会自动的把多个值封装成元组,称为序列封包. 把一个序列(列表.元组.字符串等)直接赋给多个变量,此时会把序列中的各个元素依次赋值给每个变量, ...

  9. python 序列解包(解压缩)

    序列解包(解压缩) 所学的解压缩 如果我们给出一个列表,我们需要一次性取出多个值,我们是不是可以用下面的方式实现呢? name_list = ['nick', 'egon', 'jason'] x = ...

随机推荐

  1. Python字符串格式化--format()方法

    https://blog.csdn.net/i_chaoren/article/details/77922939       csdn

  2. MVC+EF 多条件查询

    根据以前的做法是拼接sql语句,这会增加维护成本,因为sql语句里的内容不会报错,所以在使用ef的时候必须要抛弃拼接sql语句的习惯. 构建实例 List<vyw_user> list = ...

  3. springboot 不同环境切换不同的配置文件

    开发的流程是本地>测试>预发布>正式,所以不同的环境,肯定是不同的配置文件,所以我们需要针对不同的环境做不同的配置切换. 下面我们来说说 springboot 是怎么来切换的: 1. ...

  4. 学习笔记----php环境配置

    Php开发环境自定义搭建 (万事开头难) 第一步:Apache安装(httpd-2.4.37-win64-VC15.zip) 下载已编译apache安装包:Apachelounge官方下载地址:htt ...

  5. 补发————DOM与BOM

    什么是Dom? DOM是w3c(万维网联盟)的标准. DOM定义了HTML与ML文档的标准: w3c文档对象模型(DOM)是中立于平台与语言的接口,他允许程序和脚本动态访问和更新文档的内容.结构和样式 ...

  6. 插入排序之Java实现

    插入排序类似于大多数人安排扑克牌的方式. 1.从你手中的一张牌开始, 2.选择下一张卡并将其插入到正确的排序顺序中, 3.对所有的卡重复上一步. /** * * 代码理解:只需要记住两点: * 1.当 ...

  7. Spark Programming--- Shuffle operations

    *:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...

  8. 为什么要学习 Spring Boot?

    我们知道,从 2002 年开始,Spring 一直在飞速的发展,如今已经成为了在Java EE(Java Enterprise Edition)开发中真正意义上的标准,但是随着技术的发展,Java E ...

  9. Drools规则引擎入门指南(三)——使用Docker部署Workbench

    其实本来我也是打算使用Tomcat来部署Workbench的,但是在网上看了几篇文章,超级繁琐的配置.各种版本.实在看不下去了索性就直接使用Docker来部署了.本次部署的版本是最新稳定版,对应dro ...

  10. Python面向对象4:类的相关函数与属性

    1 类相关函数- issubclass:检测一个类是否是另一个类的子类- isinstance:检测一个对象是否是一个类的实例- hasattr:检测一个对象是否由成员xxx- getattr: ge ...