部分仍在编辑文章 - Python基础专题 - 深度解析python中的赋值与拷贝

简介: 部分仍在编辑文章 - Python基础专题 - 深度解析python中的赋值与拷贝

深度解析python中的赋值与拷贝


1. “与 众 不 同” 的 Python 赋 值


问题引入

【code1】以下赋值操作的输出结果是我们都可以接受的:

a = 2
b = a
b = 1
print(a)

Out[i]: 2

在对数值变量的赋值中,b = 1并没改变变量a的值。

【code2】以下对列表赋值的输出让我们意外

a = [1,2,3]
b = a
b[0] = 9
print(a)

Out[i]: [9, 2, 3]

对列表b的第0号元素赋值时,却同时改变了列表a

【code3】其实意外的结果也可以发生在对同一个变量的赋值过程中:

a = 1
print(id(a))
a = a + a
print(id(a))

Out[i]:

140710983841568
140710983841600

为了一探究竟,我决定替奥特曼深入调查一下【code1】、【code2】中小怪兽们的身份:

  • 在【code1】中,输出a,b的id:
print(id(a),id(b))
  • Out[i]: 140710983841600 140710983841568
    可以看到:a、b不再是相同id,则它们具有不同的内存地址。
  • 在【code2】中,输出a,b的id:
print(id(a),id(b))
  • Out[i]: 2613318275968 2613318275968
    可以看到:a、b仍然是同一id,可知a、b仍引用到同一内存地址。

问题刨析

Python是彻底的面向对象编程语言,从Python对象的角度来理解

研究上面的输出后,学过C++的"童鞋"第一次遇到这种“架势”可能会感到很吃惊和疑惑。

但这时应当注意,虽然也支持面向过程编程的风格,但python是一种彻底的面向对象语言,数值可以看作实例对象,比如数值1就是int类的实例。每个对象都有自己的一块内存地址。对象分为不可变对象可变对象

从本质上看Python中的直接赋值是对象的引用,传递的是对象间的地址而不是值的拷贝。

  • 先看比较容易理解的【code3】:
a = 1        # a的引用为对象1的地址
a = a + a    # 先执行语句a+a得到一个新的对象2,再执行赋值。
             # 赋值时,2是一个新的不可变对象,以前的对象(也就是1)是不可变对象,不会被改变也不能被覆盖
             # 因此a最终引用到不可变对象2的内存地址

这个过程可以绘制一个图来描述:

这就是【code3】中,变量a的id发生改变的原因。

  • 同理再看【code1】

在【code1】中,数值对象是不可变对象,在被创造之后,它的状态就不可以被改变。但尽管对象本身不可变,但变量的对象引用是可变的

a = 2    # 变量a引用到不可变对象2的地址
b = a    # b通过a的引用到2
b = 1    # b引用到另外一个对象,即数值常量1在这条语句时有系统分配的地址

这个过程可以绘制一个图来描述:

b的引用改变了,指向了另外一个不可变对象,也就是1的地址。最终a、b具有不同的内存地址。

  • 最后看【code2】

在【code2】中,列表是可变对象,对象的内容是可变的。在Python中这种可变对象一般是复合数据结构,如list、dict、tuple、set等等,其内容是可变的原因在于它们都是复合数据结构,每一个内容元素本身也是一个对象。

a = [1,2,3]  # a 获得了单条语句执行时系统分配的列表[1,2,3]的首地址(python是脚本语言)
b = a        # b引用到与a同一列表的首地址!
b[0] = 9     # 使变量b引用的列表(也是变量a引用的列表)的第一个元素引用到不可变对象9

也就是这种原因,【code2】中可变对象a在赋值时geib时,ba引用到同一对象,而赋值语句b[0] = 9仅仅是对这个数组对象的子对象b[0]的引用进行了改变,并没有改变变量b本身的引用关系

不信我们可以做如下验证:

a = [1,2,3] 
b = a
print("id(a)=",id(a),"id(b)=",id(b))
print("id(a[0])=",id(a[0]),"id(b[0])=",id(b[0]))
b[0] = 9 
print("\nid(a)=",id(a),"id(b)=",id(b))
print("id(a[0])=",id(a[0]),"id(b[0])=",id(b[0]))

Out[i]:

id(a)= 2613322289408 id(b)= 2613322289408
id(a[0])= 140710983841568 id(b[0])= 140710983841568
id(a)= 2613322289408 id(b)= 2613322289408
id(a[0])= 140710983841824 id(b[0])= 140710983841824

可以看到,a与b的id始终没变,但它们随引用的这个数组的第一个元素的引用却同时改变了,完全符合我以上所述。

其它类似疑惑

在理解了以上问题后就会理解一些类似的操作在Python简直就是扯淡,比如有人做如下谜一样的操作:

a = [1,2,3] 
a[0]=a
a

Out[i]:

[[...], 2, 3]

这结果他自己也看不懂了,就是觉得是谜一样的结果。在这个过程中,很显然没有理解到python中的赋值操作,使得列表a的头一个元素无线迭代指向原列表a自身,这样的代码几乎是没有意义的。


2. 浅拷贝与深拷贝


Python 的赋值语句不复制对象,而是创建目标和对象的绑定关系。对于自身可变,或包含可变项的集合,有时要生成副本用于改变操作,而不必改变原始对象。

2.1 浅拷贝(浅层赋值 shallow copy)

所谓浅拷贝它仅仅拷贝父对象,不会拷贝对象的内部的子对象。即浅层复制 构造一个新的复合对象,然后(在尽可能的范围内)将原始对象中找到的对象的 引用 插入其中。

2.2 深拷贝(深度复制 deep copy)

copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象。即深层复制 构造一个新的复合对象,然后,递归地将在原始对象里找到的对象的 副本 插入其中。

深度复制操作通常存在两个问题, 而浅层复制操作并不存在这些问题:

  • 递归对象 (直接或间接包含对自身引用的复合对象) 可能会导致递归循环。
  • 由于深层复制会复制所有内容,因此可能会过多复制(例如本应该在副本之间共享的数据)。

3.在函数参数传递中,赋值带来的引用问题


目录
相关文章
|
2月前
|
存储 缓存 算法
Python中collections模块的deque双端队列:深入解析与应用
在Python的`collections`模块中,`deque`(双端队列)是一个线程安全、快速添加和删除元素的双端队列数据类型。它支持从队列的两端添加和弹出元素,提供了比列表更高的效率,特别是在处理大型数据集时。本文将详细解析`deque`的原理、使用方法以及它在各种场景中的应用。
|
5天前
|
存储 SQL 缓存
阿里云大学考试python中级题目及解析-python中级
阿里云大学考试python中级题目及解析-python中级
13 0
|
6天前
|
数据采集 数据可视化 数据处理
Python从入门到精通的文章3.3.1 深入学习Python库和框架:数据处理与可视化的利器
Python从入门到精通的文章3.3.1 深入学习Python库和框架:数据处理与可视化的利器
|
7天前
|
数据采集 存储 人工智能
【Python+微信】【企业微信开发入坑指北】4. 企业微信接入GPT,只需一个URL,自动获取文章总结
【Python+微信】【企业微信开发入坑指北】4. 企业微信接入GPT,只需一个URL,自动获取文章总结
21 0
|
10天前
|
数据采集 机器学习/深度学习 数据挖掘
Python数据清洗与预处理面试题解析
【4月更文挑战第17天】本文介绍了Python数据清洗与预处理在面试中的常见问题,包括Pandas基础操作、异常值处理和特征工程。通过示例代码展示了数据读取、筛选、合并、分组统计、离群点检测、缺失值和重复值处理、特征缩放、编码、转换和降维。强调了易错点,如忽视数据质量检查、盲目处理数据、数据隐私保护、过度简化特征关系和忽视模型输入要求。掌握这些技能和策略将有助于在面试中脱颖而出。
25 8
|
13天前
|
调度 Python
Python多线程、多进程与协程面试题解析
【4月更文挑战第14天】Python并发编程涉及多线程、多进程和协程。面试中,对这些概念的理解和应用是评估候选人的重要标准。本文介绍了它们的基础知识、常见问题和应对策略。多线程在同一进程中并发执行,多进程通过进程间通信实现并发,协程则使用`asyncio`进行轻量级线程控制。面试常遇到的问题包括并发并行混淆、GIL影响多线程性能、进程间通信不当和协程异步IO理解不清。要掌握并发模型,需明确其适用场景,理解GIL、进程间通信和协程调度机制。
30 0
|
13天前
|
API Python
Python模块化编程:面试题深度解析
【4月更文挑战第14天】了解Python模块化编程对于构建大型项目至关重要,它涉及代码组织、复用和维护。本文深入探讨了模块、包、导入机制、命名空间和作用域等基础概念,并列举了面试中常见的模块导入混乱、不适当星号导入等问题,强调了避免循环依赖、合理使用`__init__.py`以及理解模块作用域的重要性。掌握这些知识将有助于在面试中自信应对模块化编程的相关挑战。
21 0
|
17天前
|
SQL API 数据库
Python中的SQLAlchemy框架:深度解析与实战应用
【4月更文挑战第13天】在Python的众多ORM(对象关系映射)框架中,SQLAlchemy以其功能强大、灵活性和易扩展性脱颖而出,成为许多开发者首选的数据库操作工具。本文将深入探讨SQLAlchemy的核心概念、功能特点以及实战应用,帮助读者更好地理解和使用这一框架。
|
18天前
|
存储 JSON JavaScript
「Python系列」Python JSON数据解析
在Python中解析JSON数据通常使用`json`模块。`json`模块提供了将JSON格式的数据转换为Python对象(如列表、字典等)以及将Python对象转换为JSON格式的数据的方法。
33 0
|
2月前
|
数据采集 数据挖掘 Python
Python中collections模块的Counter计数器:深入解析与应用
在Python的`collections`模块中,`Counter`是一个强大且实用的工具,它主要用于计数可哈希对象。无论是统计单词出现的频率,还是分析数据集中元素的分布情况,`Counter`都能提供快速且直观的结果。本文将深入解析`Counter`计数器的原理、用法以及它在实际应用中的价值。

推荐镜像

更多