31天Python入门——第10天:深入理解值传递·引用传递以及深浅拷贝问题

|----------------|
| 你好,我是安然无虞。 |

文章目录

    • [1. 什么是对象](#1. 什么是对象)
    • [2. 对象类型](#2. 对象类型)
    • [3. 引用传递](#3. 引用传递)
      • [3.1 基本概念](#3.1 基本概念)
      • [3.2 不可变对象和可变对象的引用传递](#3.2 不可变对象和可变对象的引用传递)
      • [3.3 函数参数传递中的引用传递](#3.3 函数参数传递中的引用传递)
      • [3.4 如何避免可变对象引用传递带来的问题](#3.4 如何避免可变对象引用传递带来的问题)
      • [3.5 总结: 值传递和引用传递](#3.5 总结: 值传递和引用传递)
    • [4. 深浅拷贝问题](#4. 深浅拷贝问题)
      • [4.1 浅拷贝](#4.1 浅拷贝)
      • [4.2 深拷贝](#4.2 深拷贝)
      • [4.3 使用场景](#4.3 使用场景)

1. 什么是对象

如果你学过驾驶,八成被教练骂过吧?

可能你的脑海中现在还回荡着教练粗暴的吼叫:

text 复制代码
踩离合器,
档位杆 推到1挡位置!!
慢慢抬起脚,松离合器

仔细分析上面的话,大家可以发现,我们的语言描述 通常 会涉及到 对象 :

对象 就是语言描述中涉及的 物体

比如上面的 离合器、档位杆,就是 对象,而且这是两种 不同类型 的对象

相应的,

在Python语言中也会涉及到 对象, 这些对象 包含了一定的 数据 信息

Python语言中,所有的 数据 都被称之为 对象

我们写的Python代码, 就是要 处理各种 对象 ,从而完成具体的任务

比如,我们的语句

python 复制代码
print('hello world' )

这里面 就操作了一个数据对象 hello world,这是一个字符串数据对象

真实世界的 对象 有各种类型,比如 汽车、飞机 就是不同类型的对象

程序世界里面 数据对象 也有 各种类型

比如:

字符串 hello world 是 字符串类型 的 对象

而 33 是 整数类型 的对象

2. 对象类型

Python语言中,常用的数据类型有:

  • 整数, 比如 3
  • 小数(也叫浮点数),比如 6.5
  • 字符串 , 比如 '你好'
  • 列表,比如 [1, 2, '你好']
  • 元组,比如 (1, 2, '你好')
  • 字典,比如 {1:'mike', 2:'jack'}

简单的开发任务, 这些数据类型,基本就够用了

Python语言还可以 自己定义数据类型 ,后面会学到

3. 引用传递

在 Python 中,引用传递是理解变量、对象和函数参数传递机制的关键概念.

以下是对 Python 引用传递的详细解释:

3.1 基本概念

在 Python 中,变量本质上是一个对象的名称,它指向内存中的对象. 这个指向关系就是引用. 当我们将一个变量赋值给另一个变量时,实际上是在创建一个新的引用指向同一个对象.

例如:

python 复制代码
a = [1, 2, 3]
b = a

在这个例子中,a 和 b 都是变量名,它们都指向同一个列表对象 [1, 2, 3].

b = a 这个赋值操作并不是复制了列表本身,而是让 b 也指向了 a 所指向的那个列表对象.

也就是说,变量a 和变量 b 是对同一个列表对象的两个引用.

3.2 不可变对象和可变对象的引用传递

Python当中的数据类型分为 可变对象 和 不可变对象, 它们在引用传递时的行为有所不同.

不可变对象

不可变对象包括数字、字符串、元组等, 内容一旦创建, 这些内容就无法被修改.

引用传递的表现: 当对不可变对象操作时, 如果操作会改变其内容, Python就会创建一个新的对象, 并让变量指向这个新对象.

例如:

python 复制代码
a = 10
b = a
print(id(a)) # 4509985728
print(id(b)) # 4509985728

a = 10
b = a
b = b + 1
print(id(a)) # 4491008960
print(id(b)) # 4491008992

在这个例子中, a 和 b 最初都指向同一个数字对象 10.

当执行 b = b + 1 时, b 的值变成了11, 并让 b 指向这个新对象.

而 a 仍然指向 原来的整数对象10.

对于字符串也是一样:

python 复制代码
a = "hello"
b = a
b = b + " world"

这里,a 和 b 最初都指向字符串 "hello", 执行 b = b + " world" 时,Python 创建了一个新的字符串对象 "hello world",并让 b 指向它,而 a 仍然指向 "hello".

可变对象

可变对象包括列表、字典、集合等. 这些对象的内容可以在创建后被修改.

引用传递的表现: 当对可变对象进行操作的时候, 如果操作会改变其内容, 那么所有指向该对象的变量都会受到影响.

例如:

python 复制代码
a = [1, 2, 3]
b = a
b.append(4)

在这个例子中,a 和 b 都指向同一个列表对象 [1, 2, 3].

当执行 b.append(4) 时,列表的内容被修改为 [1, 2, 3, 4]. 此时,a 和 b 都指向同一个被修改后的列表对象,因此 a 的内容也会变成 [1, 2, 3, 4].

3.3 函数参数传递中的引用传递

在Python中, 函数的参数传递也是基于引用传递的, 当我们将变量传递给函数时, 函数内部的参数变量会指向与外部变量相同的对象.

不可变对象作为参数

如果传递的是不可变对象, 那么在函数内部对参数的修改不会影响外部变量的值, 函数内部的参数变量会指向与外部变量相同的对象.

例如:

python 复制代码
def add_one(x):
    x = x + 1

a = 10
add_one(a)
print(a)  # 输出 10

在这个例子中,a 是一个整数对象 10.

当调用 add_one(a) 时,函数内部的参数 x 也指向这个整数对象 10.

在函数内部执行 x = x + 1 时,Python 创建了一个新的整数对象 11,并让 x 指向它. 但是,外部的变量 a 仍然指向原来的整数对象 10,因此在函数调用结束后,a 的值仍然是 10

所以, 这里我们可以认为不可变对象作为函数参数时其实就相当于我们经常说的值传递, 形参的改变不会影响到实参.

可变对象作为参数

如果传递的是可变对象,那么在函数内部对参数的修改会影响外部变量的值.

因为函数内部的参数变量和外部变量指向的是同一个可变对象,对这个对象的修改会直接反映到外部.

当传递可变对象(如列表)作为函数参数时,传递的是对象的引用(内存地址),而不是对象的副本.

因此,无论列表有多大,传递该列表作为参数都不会占用额外的内存.这种机制使得 Python 在处理大型数据结构时能够高效地进行函数调用.

例如:

python 复制代码
def append_element(lst, element):
    lst.append(element)

a = [1, 2, 3]
append_element(a, 4)
print(a)  # 输出 [1, 2, 3, 4]

在这个例子中,a 是一个列表对象 [1, 2, 3].

当调用 append_element(a, 4) 时,函数内部的参数 lst 也指向这个列表对象. 在函数内部执行 lst.append(4) 时,直接修改了这个列表对象的内容.

因此,外部的变量 a 也会受到影响,其值变成了 [1, 2, 3, 4].

3.4 如何避免可变对象引用传递带来的问题

如果在函数中需要对可变对象进行操作,但又不想影响外部的可变对象,可以通过创建对象的副本来进行操作.

列表的副本:

对于列表,可以使用切片操作或 copy 模块来创建副本

python 复制代码
def append_element(lst, element):
    lst = lst.copy()  # 创建列表的副本
    lst.append(element)
    return lst # 返回的是列表对象的引用, 注意哦不是其中的内容

a = [1, 2, 3]
b = append_element(a, 4)
print(a)  # 输出 [1, 2, 3]
print(b)  # 输出 [1, 2, 3, 4]

在这个例子中,通过 lst = lst.copy() 创建了列表 lst 的副本,然后对副本进行操作. 这样就不会影响外部的列表 a.

需要强调说明的地方:
上面定义的函数中返回局部变量列表时:

  • 返回的是引用(地址):当在函数外部接收返回值时,得到的是这个新列表对象的引用. 这个引用指向内存中存储该列表对象的地址.
  • 操作的是对象本身:由于返回的是引用,因此可以在函数外部通过这个引用访问和修改列表的内容.

字典的副本:

对于字典,可以使用 copy 方法来创建副本.

python 复制代码
def update_dict(d, key, value):
    d = d.copy()  # 创建字典的副本
    d[key] = value
    return d

a = {"name": "Alice", "age": 25}
b = update_dict(a, "age", 26)
print(a)  # 输出 {'name': 'Alice', 'age': 25}
print(b)  # 输出 {'name': 'Alice', 'age': 26}

在这个例子中,通过 d = d.copy() 创建了字典 d 的副本,然后对副本进行操作. 这样就不会影响外部的字典 a.

3.5 总结: 值传递和引用传递

对于不可变对象,引用传递表现为值传递的效果,因为对不可变对象的修改会创建新的对象.

而对于可变对象,引用传递会导致函数内部对对象的修改影响外部变量.

在实际编程中,需要注意引用传递带来的影响,并在必要时通过**创建副本等方式来避免问题.

4. 深浅拷贝问题

在 Python 中,深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是两种不同的对象复制方式,它们在复制对象时的行为和结果有很大区别,尤其是在处理可变数据类型(如列表、字典、集合等)时.

以下是关于深浅拷贝的详细说明:

4.1 浅拷贝

**浅拷贝是指创建一个新对象,如果字段是值类型的,那么将复制字段的值;如果字段是引用类型的,则复制引用(其实就是复制当前引用对象的地址)但不复制引用的对象. **

所以对于浅拷贝来说, 只是简单地复制一层,值类型的内容完全独立,引用类型的内容是共享的.

因此,原始对象和副本对象将引用同一个对象.

实现方式:

  • 对于列表,可以使用切片操作([:])或 copy() 方法来实现浅拷贝
  • 对于字典,可以使用 dict() 函数或 copy() 方法来实现浅拷贝
  • 对于集合,可以使用 set() 函数来实现浅拷贝
  • 对于自定义对象,可以使用 copy.copy() 方法来实现浅拷贝
python 复制代码
# 列表的浅拷贝
original_list = [1, 2, [3, 4]]
shallow_copied_list = original_list.copy()
shallow_copied_list[2].append(5)

print("Original List:", original_list)  # 输出:Original List: [1, 2, [3, 4, 5]]
print("Shallow Copied List:", shallow_copied_list)  # 输出:Shallow Copied List: [1, 2, [3, 4, 5]]

特点:

  • 浅拷贝创建的是一个新对象,但只复制了对象的第一层内容
  • 如果对象中包含可变数据类型(如列表、字典等),那么原始对象和副本对象将共享这些可变对象的引用
  • 修改副本对象中的可变数据类型时,原始对象中的对应部分也会被修改

4.2 深拷贝

深拷贝是指创建一个新对象,并且递归地复制所有对象中的对象. 也就是说,深拷贝会复制对象的所有层级,包括嵌套的对象. 因此,原始对象和副本对象是完全独立的,修改副本对象中的任何内容都不会影响原始对象.

实现方式:

  • 使用 copy.deepcopy() 方法来实现深拷贝.
python 复制代码
# 列表的深拷贝
original_list = [1, 2, [3, 4]]
deep_copied_list = copy.deepcopy(original_list)
deep_copied_list[2].append(5)
print("Original List:", original_list)  # 输出:Original List: [1, 2, [3, 4]]
print("Deep Copied List:", deep_copied_list)  # 输出:Deep Copied List: [1, 2, [3, 4, 5]]

特点:

  • 深拷贝创建的是一个完全独立的新对象,包括所有嵌套的对象
  • 修改副本对象中的任何内容都不会影响原始对象
  • 深拷贝的性能开销比浅拷贝大,因为它需要递归复制所有对象

4.3 使用场景

  • 浅拷贝:
    • 当对象中不包含嵌套的可变数据类型时,使用浅拷贝可以节省内存和时间.
    • 如果希望副本对象和原始对象共享某些数据(例如,共享一个只读的列表),可以使用浅拷贝.
  • 深拷贝:
    • 当对象中包含嵌套的可变数据类型,并且需要完全独立的副本时,使用深拷贝.
    • 如果需要对副本对象进行大量修改,而不影响原始对象,使用深拷贝是更安全的选择.

注意事项:

  • 对于不可变数据类型(如整数、浮点数、字符串、元组等),浅拷贝和深拷贝的效果是相同的,因为不可变数据类型的值是不可修改的.
  • 在使用深拷贝时,需要注意性能问题,尤其是当对象结构复杂或数据量较大时.
  • 如果对象中包含自引用(即对象引用了自身),深拷贝可能会导致无限递归,需要特别小心处理.

|----------------------|
| 遇见安然遇见你,不负代码不负卿。 |
| 谢谢老铁的时间,咱们下篇再见~ |

相关推荐
带土113 小时前
4. C++ static关键字
开发语言·c++
C++ 老炮儿的技术栈13 小时前
什么是通信规约
开发语言·数据结构·c++·windows·算法·安全·链表
@大迁世界13 小时前
TypeScript 的本质并非类型,而是信任
开发语言·前端·javascript·typescript·ecmascript
栗子叶13 小时前
Java对象创建的过程
java·开发语言·jvm
GIS之路13 小时前
GDAL 实现矢量裁剪
前端·python·信息可视化
勇哥java实战分享14 小时前
短信平台 Pro 版本 ,比开源版本更强大
后端
Amumu1213814 小时前
React面向组件编程
开发语言·前端·javascript
学历真的很重要14 小时前
LangChain V1.0 Context Engineering(上下文工程)详细指南
人工智能·后端·学习·语言模型·面试·职场和发展·langchain
IT=>小脑虎14 小时前
Python零基础衔接进阶知识点【详解版】
开发语言·人工智能·python
智航GIS14 小时前
10.6 Scrapy:Python 网页爬取框架
python·scrapy·信息可视化