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

news/2025/3/29 6:02:55/

在这里插入图片描述

你好,我是安然无虞。

文章目录

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

在这里插入图片描述

1. 什么是对象

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

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

踩离合器,
档位杆 推到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)) # 4509985728a = 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 + 1a = 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] = valuereturn da = {"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 使用场景

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

注意事项:

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

http://www.ppmy.cn/news/1583173.html

相关文章

蓝桥杯第十届 特别的数

题目描述 小明对数位中含有 2、0、1、9 的数字很感兴趣(不包括前导 0),在 1 到 40 中这样的数包括 1、2、9、10 至 32、39 和 40,共 28 个,他们的和是 574。 请问,在 1 到 n 中,所有这样的数的…

单纯形法之大M法

1. 问题背景与标准化 在求解某些线性规划问题时,往往难以直接找到初始的基本可行解。特别是当约束中存在等式或 “≥” 类型的不等式时,我们需要引入人工变量来构造一个初始可行解。 考虑如下标准形式问题(假设为最大化问题)&am…

MongoDB 的索引是提高查询性能的核心机制,类似于传统关系型数据库的索引。以下是对 MongoDB 索引的详细说明:

MongoDB 的索引是提高查询性能的核心机制,类似于传统关系型数据库的索引。以下是对 MongoDB 索引的详细说明: 一、索引基础 1. 索引的作用 加速查询:通过索引快速定位数据,避免全集合扫描(COLLSCAN)。 排…

一文详解redis

redis 5种数据类型 string 字符串是 Redis 里最基础的数据类型,一个键对应一个值。 设置值 SET key value例如: SET name "John"获取值 GET key例如: GET namelist 列表是简单的字符串列表,按插入顺序排序。 在列…

[从零开始学习JAVA] IO流下的高级流

前言: 在前面我们从IO流体系出发,分别介绍了字节流和字符流,并且详细讲解了其下沿的各种基本流,而在今天我们就要学习一下IO流中的几个高级流。 1.缓冲流 缓冲流其实就是可以让我们自己手动提供缓冲区,以此来增加读写速…

NLP高频面试题(十一)——RLHF的流程有哪些

随着大语言模型(如GPT系列)的快速发展,RLHF(Reinforcement Learning from Human Feedback,即基于人类反馈的强化学习)逐渐成为训练高质量模型的重要方法。本文将简单清晰地介绍RLHF的整体流程。 一、RLHF …

Python第六章12:序列切片练习题

# 序列切片的课后题 # 有字符串:“蛋八狗,的十八臭个这,娃芦葫爱最曼特奥” # 请使用学过的任何方式,得到“臭八十的” # 参考方法: # 1.倒序字符串,切片取出或切片取出,然后倒序 # 2.s…

1.(vue3.x+vite)创建工程

前端技术社区总目录(订阅之前请先查看该博客) 效果预览 1:使用vite生成项目模板 npm init vite-app myvue1 执行效果图: 2:package.json 调整依赖为最新版本 如下所示: 3:根目录下创建vi…