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

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 部分仍在编辑文章 - 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.在函数参数传递中,赋值带来的引用问题


目录
相关文章
|
1月前
|
Java
轻松上手Java字节码编辑:IDEA插件VisualClassBytes全方位解析
本插件VisualClassBytes可修改class字节码,包括class信息、字段信息、内部类,常量池和方法等。
107 6
|
8天前
|
数据采集 JSON API
如何利用Python爬虫淘宝商品详情高级版(item_get_pro)API接口及返回值解析说明
本文介绍了如何利用Python爬虫技术调用淘宝商品详情高级版API接口(item_get_pro),获取商品的详细信息,包括标题、价格、销量等。文章涵盖了环境准备、API权限申请、请求构建和返回值解析等内容,强调了数据获取的合规性和安全性。
|
6天前
|
数据挖掘 vr&ar C++
让UE自动运行Python脚本:实现与实例解析
本文介绍如何配置Unreal Engine(UE)以自动运行Python脚本,提高开发效率。通过安装Python、配置UE环境及使用第三方插件,实现Python与UE的集成。结合蓝图和C++示例,展示自动化任务处理、关卡生成及数据分析等应用场景。
47 5
|
19天前
|
存储 缓存 Python
Python中的装饰器深度解析与实践
在Python的世界里,装饰器如同一位神秘的魔法师,它拥有改变函数行为的能力。本文将揭开装饰器的神秘面纱,通过直观的代码示例,引导你理解其工作原理,并掌握如何在实际项目中灵活运用这一强大的工具。从基础到进阶,我们将一起探索装饰器的魅力所在。
|
21天前
|
Python 容器
[oeasy]python048_用变量赋值_连等赋值_解包赋值_unpack_assignment _
本文介绍了Python中变量赋值的不同方式,包括使用字面量和另一个变量进行赋值。通过`id()`函数展示了变量在内存中的唯一地址,并探讨了变量、模块、函数及类类型的地址特性。文章还讲解了连等赋值和解包赋值的概念,以及如何查看已声明的变量。最后总结了所有对象(如变量、模块、函数、类)都有其类型且在内存中有唯一的引用地址,构成了Python系统的基石。
28 5
|
23天前
|
Android开发 开发者 Python
通过标签清理微信好友:Python自动化脚本解析
微信已成为日常生活中的重要社交工具,但随着使用时间增长,好友列表可能变得臃肿。本文介绍了一个基于 Python 的自动化脚本,利用 `uiautomator2` 库,通过模拟用户操作实现根据标签批量清理微信好友的功能。脚本包括环境准备、类定义、方法实现等部分,详细解析了如何通过标签筛选并删除好友,适合需要批量管理微信好友的用户。
32 7
|
25天前
|
XML 数据采集 数据格式
Python 爬虫必备杀器,xpath 解析 HTML
【11月更文挑战第17天】XPath 是一种用于在 XML 和 HTML 文档中定位节点的语言,通过路径表达式选取节点或节点集。它不仅适用于 XML,也广泛应用于 HTML 解析。基本语法包括标签名、属性、层级关系等的选择,如 `//p` 选择所有段落标签,`//a[@href='example.com']` 选择特定链接。在 Python 中,常用 lxml 库结合 XPath 进行网页数据抓取,支持高效解析与复杂信息提取。高级技巧涵盖轴的使用和函数应用,如 `contains()` 用于模糊匹配。
|
25天前
|
测试技术 开发者 Python
使用Python解析和分析源代码
本文介绍了如何使用Python的`ast`模块解析和分析Python源代码,包括安装准备、解析源代码、分析抽象语法树(AST)等步骤,展示了通过自定义`NodeVisitor`类遍历AST并提取信息的方法,为代码质量提升和自动化工具开发提供基础。
42 8
|
1月前
|
数据可视化 图形学 Python
在圆的外面画一个正方形:Python实现与技术解析
本文介绍了如何使用Python的`matplotlib`库绘制一个圆,并在其外部绘制一个正方形。通过计算正方形的边长和顶点坐标,实现了圆和正方形的精确对齐。代码示例详细展示了绘制过程,适合初学者学习和实践。
43 9
|
29天前
|
存储 Python 容器
[oeasy]python045_[词根溯源]赋值_assignment_usage_使用
本文回顾了上一次讲解的内容,重点讨论了变量的概念及其在各种系统和游戏中的应用。文章详细解释了变量的声明与赋值操作,强调了赋值即为将具体值存储到变量名下的过程。同时,通过例子说明了字面量(如数字0)不能被赋值给其他值的原因。此外,还探讨了“赋值”一词的来源及其英文表达“assignment”的含义,并简要介绍了与之相关的英语词汇,如sign、assign、signal等。最后,总结了本次课程的核心内容,即赋值操作的定义和实现方式。
24 3