一、深浅拷贝问题
在实际工作中,经常涉及到数据的传递,在数据传递使用过程中,可能会发生数据被修改的问题。为了防止数据被修改,就需要在传递一个副本,即使副本被修改,也不会影响原数据的使用。为了生成这个副本,就产生了拷贝。下面先了解一下几个概念:对象、可变类型、引用
Python对象:在 Python 中,对象有一种很通俗的说法是,万物皆对象。说的就是构造的任何数据类型都是一个对象,无论是数字,字符串,还是函数,甚至是模块,Python都对当做对象处理。所有 Python 对象都拥有三个属性:身份、类型、值。看一个简单的例子:
可变与不可变对象:在Python中,按更新对象的方式,可以将对象分为 2 大类:可变对象与不可变对象。
- 可变对象: 列表、字典、集合,所谓可变是指可变对象的值可变,身份是不变的。
- 不可变对象:数字、字符串、元组,不可变对象就是对象的身份和值都不可变。新创建的对象被关联到原来的变量名,旧对象被丢弃,垃圾回收器会在适当的时机回收这些对象。
引用:在 Python 程序中,每个对象都会在内存中申请开辟一块空间来保存该对象,该对象在内存中所在位置的地址被称为引用。在开发程序时,所定义的变量名实际就对象的地址引用。
引用实际就是内存中的一个数字地址编号,在使用对象时,只要知道这个对象的地址,就可以操作这个对象,但是因为这个数字地址不方便在开发时使用和记忆,所以使用变量名的形式来代替对象的数字地址。 在 Python 中,变量就是地址的一种表示形式,并不开辟开辟存储空间。
就像 IP 地址,在访问网站时,实际都是通过 IP 地址来确定主机,而 IP 地址不方便记忆,所以使用域名来代替 IP 地址,在使用域名访问网站时,域名被解析成 IP 地址来使用。
通过一个例子来说明变量和变量指向的引用:
- 基本类型和引用类型数据拷贝的问题。因为基本类型的数据大小是固定的,所以他保存在栈内存中;而引用类型的数据大小不固定,因而保存在堆内存中,单引用类型在栈内存中只保存一个指向堆内存的指针。
- 浅拷贝:对于浅拷贝来说,如果拷贝基本类型,那么就等于赋值一样,会直接拷贝其本身;但如果拷贝的是引用类型,就只会拷贝一层,如果原对象发生改变,那么拷贝对象也会发生改变。
- 深拷贝:深拷贝的话就会拷贝多层,嵌套的对象也会被拷贝出来,相当于开辟一个新的内存地址用于存放拷贝的对象。在 python 语言中没有明显的指出,例如,操作指针或对于指针的操作。
- 拷贝就是一个变量的值传给另外一个变量。在 python 中 id() 方法可以查看存放变量的内存地址,这为我们下面理解深浅 copy 提供了便利。
- 浅拷贝是指把存放变量的地址值传给被赋值,最后两个变量引用了同一份地址,如上图所示。
- 深拷贝是指被赋值的变量开辟了另一块地址用来存放要赋值的变量的值(内容)。在 python 中引用 copy 模块,copy模块中有 deepcopy() 方法,调用它完成变量的深copy,观察变量地址如下:
浅拷贝只拷贝顶层引用,遇到引用类型,只是复制了个引用,修改了副本中引用类型里的数据,原数据也会改变,示例如下:
深拷贝会逐层进行拷贝,直到拷贝的所有引用都是不可变引用为止,示例如下:
lst1= [1, [6, 7, 8], 3] lst1# [1, [6, 7, 8], 3]lst2=copy.deepcopy(lst1) lst2# [1, [6, 7, 8], 3]lst2[1][1] =996print("副本:", lst2) print("原始:", lst1) # 副本: [1, [6, 996, 8], 3]# 原始: [1, [6, 7, 8], 3]
在深拷贝中,修改了副本中引用类型里的数据,原数据不会改变。
总结如下:
- Python默认的拷贝方式是浅拷贝:因为浅拷贝花费时间更少、花费内存更少、浅拷贝只拷贝顶层数据,一般情况下比深拷贝效率高。大多数情况下,编写程序时,都是使用浅拷贝,除非有特定的需求。
- Python中有多种方式实现浅拷贝,copy模块的 copy 函数 ,对象的 copy 函数 ,工厂方法,切片等。
- 不可变对象在赋值时会开辟新空间;可变对象在赋值时,修改一个的值,另一个也会发生改变。深、浅拷贝对不可变对象拷贝时,不开辟新空间,相当于赋值操作。
- 浅拷贝在拷贝时,只拷贝第一层中的引用,如果元素是可变对象,并且被修改,那么拷贝的对象也会发生变化;深拷贝在拷贝时,会逐层进行拷贝,直到所有的引用都是不可变对象为止。
二、递归函数练习
1. 求阶乘
deffactorial(n): return1ifn==1elsen*factorial(n-1) factorial(5)
结果如下:
2. 猴子吃桃问题
猴子第一天摘下若干个桃子,当即吃了一半,还不瘾,又多吃了一个。第二天早上又将剩下的桃子吃掉一半,又多吃了一个。以后每天早上都吃了前一天剩的一半零一个。到第 10 天早上想再吃时,见只剩下一个桃子了,求第一天共摘了多少桃子?
# 第10天早上想再吃时,见只剩下一个桃子了,说明第9天的时候就只剩一个桃子了defeat_peach(n): return1ifn==2else2* (eat_peach(n-1) +1) eat_peach(10)
结果如下:
3. 打印斐波那契数列
# -*- coding: UTF-8 -*-"""@Author :叶庭云@CSDN :https://yetingyun.blog.csdn.net/"""deffibonacii(n): returnnifn<=1else (fibonacii(n-1) +fibonacii(n-2)) x=int(input("输出前几项?: ")) ifx<=0: print("请输入输入正数!") else: print("斐波那契数列前{}项:".format(x), end=" ") foriinrange(1, x+1): print(fibonacii(i), end=" ")
结果如下: