python基础之函数
一、函数概述
- 定义:函数是一堆代码的集合体,用于完成某种功能,相当于工具。
- 原则:先定义,后调用。
def foo():
bar()
print('from foo')
def bar():
print('from bar')
foo()
# 输出结果:
from bar
from foo
# 注意:此处并不会报错,因为函数在定义阶段只检查语法错误,当调用foo函数的时候,bar函数已经载入到了内存中,所以即使bar的定义在foo后面,调用的时候也能找到bar的函数代码,因此能正常运行。
二、函数应用场景
- 代码过长时,组织结构不清晰,可读性差,可以通过函数分块;
- 代码冗余,对于重复性代码,一次定义,多次调用;
- 使用函数可增强代码的可维护性和扩展性。
三、函数的定义
- 先定义(大致有三种方式)
- 无参函数
def 函数名(): '''函数功能注释''' # 注释 函数内代码块 return 返回值 # 非必须
- 有参函数
def 函数名(参数1, 参数2, ...): '''函数功能注释''' # 注释 函数内代码块 return 返回值 # 非必须
- 空函数(一般用于前期搭建框架使用)
def 函数名(): pass
- 后调用
【调用函数发生的事情】通过函数名找到函数的内存地址,然后加括号就是在触发函数体代码的执行。函数名() # 方式一:无参函数调用 函数名(参数1,参数2, ...) # 方式二:有参函数调用
- 应用场景
- 直接调用;
- 作为表达式的一部分;
- 作为返回值;
- 作为可变类型的元素;
- 作为函数的参数。
四、函数的分类
- 内置函数:python解释器内已经定义好的函数,可以直接调用;
- 自定义函数:自己定义的函数,需要自己实现函数的所有功能,才能调用;
五、函数名
- 函数名的命名规则跟变量相同;
- 函数名可以看做是一个变量,可以被赋值,则该函数将指向新赋值的内存地址,不再指向函数代码块内存地址,因此定义函数时注意函数名不能使用内置函数名,否则内置函数会被覆盖;
# 示例:
# len内置函数用于计算值得长度
v1 = len("武沛齐")
print(v1) # 3
# len重新定义成另外一个函数
def len(a1,a2):
return a1 + a2
# 以后执行len函数,只能按照重新定义的来使用
v3 = len(1,2)
print(v3)
- 函数名可以赋值给其他变量,则被赋值变量会指向函数代码块内存地址,该变量也可代指该函数,通过变量名加括号同样可以执行该函数;
# 示例1:
def func(a1,a2):
print(a1,a2)
func_list = [func,func,func]
func(11,22)
func_list[0](11,22)
func_list[1](33,44)
func_list[2](55,66)
# ***********************
# 示例2:
def func(a1,a2):
print(a1,a2)
# 执行func函数
func(11,22)
# func重新赋值成一个字符串
func = "武沛齐"
print(func)
- 函数名可以作为函数的参数、返回值;
# 示例1:做参数
def plus(num):
return num + 100
def handler(func):
res = func(10) # 110
msg = "执行func,并获取到的结果为:{}".format(res)
print(msg) # 执行func,并获取到的结果为:110
# 执行handler函数,将plus作为参数传递给handler的形式参数func
handler(plus)
# ***********************************************
# 示例2:做返回值
def plus(num):
return num + 100
def handler():
print("执行handler函数")
return plus
result = handler()
data = result(20) # 120
print(data)
- 函数可被哈希,所以函数名也可以当做集合的元素、字典的键,也可以作为字典、元组、列表、集合的元素。
# 示例
def send_msg(mobile, content):
"""发送短信"""
pass
def send_email(to_email, subject, content):
"""发送图片"""
pass
def send_wechat(user_id, content):
"""发送微信"""
pass
func_list = [
{"name": send_msg, "params": {'mobile': "15131255089", "content": "你有新短消息"}},
{"name": send_email, "params": {'to_email': "wupeiqi@live.com", "subject": "报警消息", "content": "硬盘容量不够用了"}},
{"name": send_wechat, "params": {'user_id': 1, 'content': "约吗"}},
]
# {"name": send_msg, "params": {'mobile': "15131255089", "content": "你有新短消息"}},
for item in func_list:
func = item['name'] # send_msg
param_dict = item['params'] # {'mobile': "15131255089", "content": "你有新短消息"}
func(**param_dict) # send_msg(**{'mobile': "15131255089", "content": "你有新短消息"})
六、函数的返回值
- return:函数结束的标志,即函数体运行到return时会立刻终止函数的执行。
- 返回值分类:
- return:返回None
- return None : 返回None
- 不写return:返回None
- return a :返回变量a的内存地址。(a可以是变量、具体的数值、函数名等任意类型)
- return a, b : 返回元组(a, b)
七、函数的参数
1. 函数的参数分为形参和实参:
- 形参:函数定义时在函数名后面的括号内定义的变量;
- 实参:函数被调用时传入的变量或值。
2. 形参的类型:
- 普通形参:函数定义中只是定义一个变量名,调用函数时必须传值;
- 默认值形参:函数定义时不但定义了变量名,同时还赋值,调用函数时可以不传该参数;
- 动态形参:
- *args:只接受任意多的位置实参,将传入的所有位置实参组成一个元组,传递给args,因此args的值必定是一个空值或元组;
- **kwargs:只接受任意多的关键字实参,将传入的关键字实参以字典的形式传递给kwargs,因此kwargs必定是一个空值或字典。
3. 实参的类型:
- 位置参数:对应普通形参和默认值形参,顺序必须一一对应;
- 关键字参数:位置可以不固定。
def func(*arg, **kwargs):
print(arg, kwargs)
func(3,4,5,x=66,y=77)
# 输出结果:
>>> (3, 4, 5) {'x':66, 'y':77}
【注意】函数调用时,传参也可以使用 * 和 ** 。 * 后面可以是列表、元组或集合,传值时会将其解压成位置实参传递,** 后面为字典,传值时会将其解压成关键字实参传递。
def func(a, b, c, x, y):
print(a, b, c, x, y)
func(*(1,2,3), **{'x':4, 'y':5})
# 输出结果:
>>> 1, 2, 3, 4, 5
4. 形参的顺序:
- 普通参数必须放在最前面;
- **kwargs必须放在最后面;
- 动态参数必须在普通参数后面;
- **kwargs必须在*args后面;
- 默认值参数与*args的位置可以互换。
实参的顺序:
形参的顺序确定后,实参的顺序跟形参保持一致。
def func(a, b, *args, c=9, **kwargs):
print(a, b, c, args, kwargs)
func(1, 2, 99, 77, x = 4, y = 5)
#输出结果:
>>> 1 2 9 (99, 77) {'x': 4, 'y': 5}
def func(a, b, c=9, *args, **kwargs):
print(a, b, c, args, kwargs)
func(1, 2, 99, 77, x = 4, y = 5)
#输出结果:
>>> 1 2 99 (77,) {'x': 4, 'y': 5}
5. 传参
函数执行传参时,传递的是内存地址。优点:
- 节省内存。不用将实参复制一份再传值;
- 对于可变类型(列表、字典、集合)并且函数中修改元素的内容,所有的使用该变量的地方都会修改;如果函数内重新赋值,则不影响外面的可变类型的值;【参看后面坑1】
- 对于不可变类型(例如字符串),无法修改内部元素,只能重新赋值;
八、名称空间和作用域
名称空间
- 名称空间:存放名字的地方,是对栈区的划分。
- 分类:
- 内置名称空间:存放python解释器内置的名字,解释器启动时产生,关闭则销毁;
- 全局名称空间:只要不是函数内定义的、也不是内置的,剩下的都是全局名称空间的名字,因此,for、while、if语句块中定义的变量都属于全局名称空间,全局有效,语句块执行完毕后,变量不会被销毁;python文件执行则产生,运行完毕后销毁;
- 局部名称空间:函数内的名字,调用函数时存活,函数调用完毕后则销毁(函数嵌套时有不销毁的特殊情况)。
- 加载顺序:
内置 --> 全局 --> 局部 - 销毁顺序
局部 --> 全局 --> 内置 - 查找顺序:从当前所在的位置向外层一层一层查找
查找优先级:
局部 --> 全局 --> 内置
名称空间的“嵌套”关系是以函数定义阶段为准,与调用位置无关。
示例:
- 分类:
作用域
- 作用域:名字的作用范围。python是以函数为作用域,所以在函数内创建的所有数据,可以此函数中被使用,无法在其他函数中被使用。
- 变量作用域:
- 全局变量:全局作用域中创建的变量,可全局使用,也可以在局部作用域中被使用,一般都使用大写字母命名;
- 局部变量:在函数内创建的变量称为局部变量,局部变量只能在局部作用域中被使用。
- 局部作用域中使用全局作用域的变量:局部作用域中只能对全局变量进行读取和修改内部元素(可变类型)的操作,无法对全局变量进行重新赋值,如果非要重新赋值,需要在局部作用域用global关键字修饰该变量,才能重新赋值。示例如下:
COUNTRY = "中国" CITY_LIST = ["北京","上海","深圳"] def download(): url = "http://www.xxx.com" global CITY_LIST CITY_LIST = ["河北","河南","山西"] print(CITY_LIST) global COUNTRY COUNTRY = "中华人民共和国" print(COUNTRY) def upload(): file_name = "rose.zip" print(COUNTRY) print(CITY_LIST) download() upload()
- 变量作用域:
【补充】nolocal关键字
其实,还有一个nolocal关键字,用的比较少,此处作为了解即可。nonlocal修饰的变量指向上一级作用域的变量。
name = 'root'
def outer():
name = "武沛齐"
def inner():
nonlocal name
name = 123
inner()
print(name)
outer()
print(name)
name = 'root'
def outer():
name = 'alex'
def func():
name = "武沛齐"
def inner():
nonlocal name
name = 123
inner()
print(name)
func()
print(name)
outer()
print(name)
name = 'root'
def outer():
name = 'alex'
def func():
nonlocal name
name = "武沛齐"
def inner():
nonlocal name
name = 123
inner()
print(name)
func()
print(name)
outer()
print(name)
九、函数嵌套
- 定义:函数可以定义在局部作用域,这样函数可以被局部作用域和其子作用域中调用。
# 示例:
def func():
print("沙河高晓松")
def handler():
print("昌平吴彦祖")
def inner():
print("朝阳大妈")
inner()
func()
print("海淀网友")
handler()
- 应用场景
其实,大多数情况下我们都会将函数定义在全局,不会嵌套着定义函数。不过,当我们定义一个函数去实现某功能,想要将内部功能拆分成N个函数,又担心这个N个函数放在全局会与其他函数名冲突时(尤其多人协同开发)可以选择使用函数的嵌套。 - 函数嵌套作用域分析方法
- 优先在自己的作用域找,自己没有就去上级作用域。
- 在作用域中寻找值时,要确保此次此刻值是什么。
- 分析函数的执行,并确定函数
作用域链
。(函数嵌套)
闭包
- 闭包
- 定义:闭包,简而言之就是将数据封装在一个包(区域)中,使用时再去里面取。(本质上 闭包是基于函数嵌套搞出来一个中特殊嵌套)
- 应用场景
- 场景一:封装数据防止污染全局。
def func(age): name = "武沛齐" def f1(): print(name, age) def f2(): print(name, age) def f3(): print(name, age) f1() f2() f3() func(123)
- 场景二:封装数据封到一个包里,使用时再取。
案例如下:def task(arg): def inner(): print(arg) return inner inner_func_list = [] for val in [11,22,33]: inner_func_list.append( task(val) ) inner_func_list[0]() # 11 inner_func_list[1]() # 22 inner_func_list[2]() # 33
- 场景一:封装数据防止污染全局。
装饰器
- 装饰器
- 定义:装饰器,在不修改原函数内容的前提下,通过@函数可以实现在函数前后自定义执行一些功能(批量操作会更有意义)。
- 实现原理:基于@语法和函数闭包,将原函数封装在闭包中,然后将函数赋值为一个新的函数(内层函数),执行函数时再在内层函数中执行闭包中的原函数。
- 实现效果:可以在不改变原函数内部代码 和 调用方式的前提下,实现在函数执行和执行扩展功能。
- 适用场景:多个函数统一在执行前后自定义一些功能。
- 示例:基于装饰器实现的伪代码:
from flask import Flask app = Flask(__name__) def auth(func): def inner(*args, **kwargs): # 在此处,判断如果用户是否已经登录,已登录则继续往下,未登录则自动跳转到登录页面。 return func(*args, **kwargs) return inner @auth def index(): return "首页" @auth def info(): return "用户中心" @auth def order(): return "订单中心" def login(): return "登录页面" app.add_url_rule("/index/", view_func=index, endpoint='index') app.add_url_rule("/info/", view_func=info, endpoint='info') app.add_url_rule("/order/", view_func=order, endpoint='order') app.add_url_rule("/login/", view_func=login, endpoint='login') app.run()
- 重要补充:functools:其实,一般情况下大家不用functools也可以实现装饰器的基本功能,但后期在项目开发时,不加functools会出错(内部会读取
__name__
,且__name__
重名的话就报错),所以在此大家就要规范起来自己的写法。 - 【装饰器模板】 实际使用时套用该格式即可:
import functools def auth(func): @functools.wraps(func) def inner(*args, **kwargs): """原函数执行前需要执行的代码""" res = func(*args, **kwargs) # 执行原函数 """原函数执行后需要执行的代码""" return res return inner
十、匿名函数、三元运算、推导式
-
定义:匿名函数,则是基于lambda表达式实现定义一个可以没有名字的函数。
-
格式:lambda 参数:函数体
- 参数,支持任意参数。
lambda x: 函数体 lambda x1,x2: 函数体 lambda *args, **kwargs: 函数体
- 函数体,只能支持单行的代码。
- 返回值,默认将函数体单行代码执行的结果返回给函数的执行这。
func = lambda x: x + 100 v1 = func(10) print(v1) # 110
- 参数,支持任意参数。
-
简单的函数,可以基于lambda表达式实现。
def func(data): return data.replace("苍老师","***") func= lambda data: data.replace("苍老师","***")
-
【扩展】三元运算
- 格式:结果 = 条件成立时 if 条件 else 不成立
- 示例:匿名函数和三元运算的综合运用:
func = lambda x: "大了" if x > 66 else "小了" v1 = func(1) print(v1) # "小了" v2 = func(100) print(v2) # "大了"
-
推导式
推导式是Python中提供了一个非常方便的功能,可以让我们通过一行代码实现创建list、dict、tuple、set 的同时初始化一些值。-
列表
num_list = [ i for i in range(10)] num_list = [ [i,i] for i in range(10)] num_list = [ [i,i] for i in range(10) if i > 6 ]
-
集合
num_set = { i for i in range(10)} num_set = { (i,i,i) for i in range(10)} num_set = { (i,i,i) for i in range(10) if i>3}
-
字典
num_dict = { i:i for i in range(10)} num_dict = { i:(i,11) for i in range(10)} num_dict = { i:(i,11) for i in range(10) if i>7}
-
元组,不同于其他类型,会得到一个生成器。
# 不会立即执行内部循环去生成数据,而是得到一个生成器。 data = (i for i in range(10)) print(data) for item in data: print(item)
-
【烧脑面试题】
def num():
return [lambda x: i * x for i in range(4)]
# 1. num()并获取返回值 [函数,函数,函数,函数] i=3
# 2. for循环返回值
# 3. 返回值的每个元素(2)
result = [m(2) for m in num()] # [6,6,6,6]
print(result)
def num():
return (lambda x: i * x for i in range(4))
# 1. num()并获取返回值 生成器对象
# 2. for循环返回值
# 3. 返回值的每个元素(2)
result = [m(2) for m in num()] # [0,2,4,6 ]
print(result)
十一、生成器函数
-
定义:生成器是由函数+yield关键字创造出来的写法,在特定情况下,用他可以帮助我们节省内存。
- 生成器函数,但函数中有yield存在时,这个函数就是生产生成器函数。
- 生成器对象,执行生成器函数时,会返回一个生成器对象。
- 生成器的特点是,记录在函数中的执行位置,下次执行next时,会从上一次的位置基础上再继续向下执行。
def func(): print(111) yield 1 print(222) yield 2 print(333) yield 3 print(444) data = func() # 执行生成器函数func,返回的是生成器对象。 # 注意:执行生成器函数时,函数内部代码不会立刻执行。
-
生成器对象的调用
- 使用 next() 函数
def func(): print(111) yield 1 print(222) yield 2 print(333) yield 3 print(444) data = func() v1 = next(data) print(v1) v2 = next(data) print(v2) v3 = next(data) print(v3) v4 = next(data) print(v4) # 结束或中途遇到return,程序爆:StopIteration 错误
- 使用 for 循环
data = func() for item in data: print(item)
- 使用生成器对象的send()函数【几乎用不到】
def func(): print(111) v1 = yield 1 print(v1) print(222) v2 = yield 2 print(v2) print(333) v3 = yield 3 print(v3) print(444) data = func() n1 = data.send(None) print(n1) n2 = data.send(666) print(n2) n3 = data.send(777) print(n3) n4 = data.send(888) print(n4)
-
应用场景举例:
-
假设要让你生成 300w个随机的4位数,并打印出来。
- 在内存中一次性创建300w个
- 动态创建,用一个创建一个。
import random val = random.randint(1000, 9999) print(val)
import random data_list = [] for i in range(300000000): val = random.randint(1000, 9999) data_list.append(val) # 再使用时,去 data_list 中获取即可。 # ...
import random def gen_random_num(max_count): counter = 0 while counter < max_count: yield random.randint(1000, 9999) counter += 1 data_list = gen_random_num(3000000) # 再使用时,去 data_list 中获取即可。
-
-
总结:所以,当以后需要我们在内存中创建很多数据时,可以想着用基于生成器来实现一点一点生成(用一点生产一点),以节省内存的开销。
【补充】 yield from(开发中不常用)
在生成器部分我们了解了yield关键字,其在python3.3之后有引入了一个yield from。相当于yield嵌套。执行到foo()时,调到foo函数执行,执行完再回到func继续执行。
def foo():
yield 2
yield 2
yield 2
def func():
yield 1
yield 1
yield 1
yield from foo()
yield 1
yield 1
for item in func():
print(item)
十二、【内置函数】
第一组(5个)
-
abs,绝对值
v = abs(-8) # 8
-
pow,指数
v1 = pow(2,3) # 2的3次方 2**3 print(v1) # 8
-
sum,求和
v1 = sum([-11, 22, 33, 44, 55]) # 可以被迭代-for循环 print(v1)
-
divmod,求商和余数
v1, v2 = divmod(9, 2) print(v1, v2) # v1,v2分别接收商和余数
-
round,小数点后n位(四舍五入)
v1 = round(4.11786, 2) print(v1) # 4.12
第二组(4个)
-
min,最小值
v1 = min(11, 2, 3, 4, 5, 56) print(v1) # 2
v2 = min([11, 22, 33, 44, 55]) # 迭代的类型(for循环) print(v2)
v3 = min([-11, 2, 33, 44, 55], key=lambda x: abs(x)) print(v3) # 2
-
max,最大值
v1 = max(11, 2, 3, 4, 5, 56) print(v1) v2 = max([11, 22, 33, 44, 55]) print(v2)
v3 = max([-11, 22, 33, 44, 55], key=lambda x: x * 10) print(v3) # 55
-
all,是否全部为True
v1 = all( [11,22,44,""] ) # False
-
any,是否存在True
v2 = any([11,22,44,""]) # True
第三组(3个)
- bin,4种进制整数转二进制
- oct,4种进制整数转八进制
- hex,4种进制整数转十六进制
第四组(2个)
-
ord,获取字符对应的unicode码点(十进制)
v1 = ord("武") print(v1, hex(v1))
-
chr,根据码点(十进制)获取对应字符
v1 = chr(27494) print(v1)
第五组(9个)
-
int,可接收4种进制整数或字符串
int(0x7e) int('0x7e', 16) # 传入非十进制字符串时,必须指定进制的数值。
-
float,可接收的参数:
(1)4种进制整数; (2)boo值,True则为1.0,False则为0.0; (3)十进制数字的字符串,float('32'), float('3.2')
-
str,unicode编码
(1)接收4中进制整数,返回十进制字符串 (2)接收其他类型,直接返回原格式字符串形式。
-
bytes,utf-8、gbk编码
v1 = "武沛齐" # str类型 v2 = v1.encode('utf-8') # bytes类型 v3 = bytes(v1,encoding="utf-8") # bytes类型
-
bool
None、0、空字符串、空列表、空字典、空集合、空元组返回False,其他返回True。
-
list
-
dict
-
tuple
-
set
第六组(13个)
-
len
-
print
-
input
-
open
-
type,获取数据类型
v1 = "123" if type(v1) == str: pass else: pass
-
range
range(10) range(1, 10, 2) # 步长 range(10, 1, -1) # 逆向
-
enumerate
v1 = ["武沛齐", "alex", 'root'] for num, value in enumerate(v1, 1): print(num, value)
-
id,返回内存地址
-
hash,哈希函数
v1 = hash("武沛齐")
-
help,帮助信息
- pycharm,不用
- 终端,使用
-
zip,拉链函数
v1 = [11, 22, 33, 44, 55, 66] v2 = [55, 66, 77, 88] v3 = [10, 20, 30, 40, 50] result = zip(v1, v2, v3) for item in result: print(item)
-
callable,是否可执行,后面是否可以加括号。
v1 = "武沛齐" v2 = lambda x: x def v3(): pass print( callable(v1) ) # False print(callable(v2)) print(callable(v3))
-
sorted,排序
v1 = sorted([11,22,33,44,55])
info = { "wupeiqi": { 'id': 10, 'age': 119 }, "root": { 'id': 20, 'age': 29 }, "seven": { 'id': 9, 'age': 9 }, "admin": { 'id': 11, 'age': 139 }, } result = sorted(info.items(), key=lambda x: x[1]['id']) print(result)
data_list = [ '1-5 编译器和解释器.mp4', '1-17 今日作业.mp4', '1-9 Python解释器种类.mp4', '1-16 今日总结.mp4', '1-2 课堂笔记的创建.mp4', '1-15 Pycharm使用和破解(win系统).mp4', '1-12 python解释器的安装(mac系统).mp4', '1-13 python解释器的安装(win系统).mp4', '1-8 Python介绍.mp4', '1-7 编程语言的分类.mp4', '1-3 常见计算机基本概念.mp4', '1-14 Pycharm使用和破解(mac系统).mp4', '1-10 CPython解释器版本.mp4', '1-1 今日概要.mp4', '1-6 学习编程本质上的三件事.mp4', '1-18 作业答案和讲解.mp4', '1-4 编程语言.mp4', '1-11 环境搭建说明.mp4' ] result = sorted(data_list, key=lambda x: int(x.split(' ')[0].split("-")[-1]) ) print(result)
十三、函数相关的坑
函数定义中存在默认值参数时,python在创建函数(未执行)时,如果发现函数有默认值参数时,则会在函数内部创建一块区域并维护这个默认值(使用a代指)。
函数被调用过程中:
- 对于默认值为不可变类型,调用过程不会修改这个维护的默认值;
- 对于默认值为可变类型,如果调用未给该默认值参数传值,则使用a,如果函数内部修改这个值而非重新赋值的情况下,则这个值会被修改,下次不传参调用函数时,使用的是修改后的a,因此,后面每次这样的调用都可能修改a的值,需特别注意。如果调用时给默认值参数传值,则不会影响 a 的值。
示例:
# 内存中创建空间存储 [1, 2] ,假设内存地址为:10000001
def func(a1, a2=[1, 2]):
a2.append(a1)
return a2
# 未给默认值参数传值,a1=10
# a2 -> 10000001
# v1 -> 10000001
# 执行完成后,10000001地址的值为 [1, 2, 10]
v1 = func(10)
# 未给默认值参数传值,a1=20
# a2 -> 10000001
# v2 -> 10000001
# 执行完成后,10000001地址的值为 [1, 2, 10, 20]
v2 = func(20)
# 给默认值参数传值[11, 22],假设内存地址为11111110,a1=30
# a2 -> 11111110 [11,22,30]
# v3 -> 11111110
# 执行完成后,11111110 值为[11, 22, 30] , 10000001 值仍为[1, 2, 10, 20]
v3 = func(30, [11, 22])
# 未给默认值参数传值,a1=40
# a2 -> 10000001
# v4 -> 10000001
# 执行完成后,10000001 值为[1, 2, 10, 20, 40]
v4 = func(40)
# 执行到此处,v1、v2、v4都指向10000001的内存地址,因此值都为
# [1, 2, 10, 20, 40],v3指向新开辟的内存地址 11111110,因此值为 [11,22,30]。
print(v1) # [1, 2, 10, 20, 40]
print(v2) # [1, 2, 10, 20, 40]
print(v3) # [11,22,30]
print(v4) # [1, 2, 10, 20, 40]
【补充】深浅拷贝
-
浅拷贝
-
不可变类型,不拷贝。
import copy v1 = "武沛齐" print(id(v1)) # 140652260947312 v2 = copy.copy(v1) print(id(v2)) # 140652260947312
按理说拷贝v1之后,v2的内存地址应该不同,但由于python内部优化机制,内存地址是相同的,因为对不可变类型而言,如果以后修改值,会重新创建一份数据,不会影响原数据,所以,不拷贝也无妨。
-
可变类型,只拷贝第一层。
import copy v1 = ["武沛齐", "root", [44, 55]] print(id(v1)) # 140405837216896 print(id(v1[2])) # 140405837214592 v2 = copy.copy(v1) print(id(v2)) # 140405837214784 print(id(v2[2])) # 140405837214592
-
-
深拷贝
-
不可变类型,不拷贝
import copy v1 = "武沛齐" print(id(v1)) # 140188538697072 v2 = copy.deepcopy(v1) print(id(v2)) # 140188538697072
特殊的元组:
-
元组元素中无可变类型,不拷贝
import copy v1 = ("武沛齐", "root") print(id(v1)) # 140243298961984 v2 = copy.deepcopy(v1) print(id(v2)) # 140243298961984
-
元素元素中有可变类型,找到所有【可变类型】或【含有可变类型的元组】 均拷贝一份
import copy v1 = ("武沛齐", "root", [11, [44, 55], (11, 22), (11, [], 22), 33]) v2 = copy.deepcopy(v1) print(id(v1)) # 140391475456384 print(id(v2)) # 140391475456640 print(id(v1[2])) # 140352552779008 print(id(v2[2])) # 140352552920448 print(id(v1[2][1])) # 140642999940480 print(id(v2[2][1])) # 140643000088832 print(id(v1[2][2])) # 140467039914560 print(id(v2[2][2])) # 140467039914560 print(id(v1[2][3])) # 140675479841152 print(id(v2[2][3])) # 140675480454784
-
-
可变类型,找到所有层级的 【可变类型】或【含有可变类型的元组】 均拷贝一份
import copy v1 = ["武沛齐", "root", [11, [44, 55], (11, 22), (11, [], 22), 33]] v2 = copy.deepcopy(v1) print(id(v1)) # 140391475456384 print(id(v2)) # 140391475456640 print(id(v1[2])) # 140352552779008 print(id(v2[2])) # 140352552920448 print(id(v1[2][1])) # 140642999940480 print(id(v2[2][1])) # 140643000088832 print(id(v1[2][2])) # 140467039914560 print(id(v2[2][2])) # 140467039914560 print(id(v1[2][3])) # 140675479841152 print(id(v2[2][3])) # 140675480454784
import copy v1 = ["武沛齐", "root", [44, 55]] v2 = copy.deepcopy(v1) print(id(v1)) # 140405837216896 print(id(v2)) # 140405837214784 print(id(v1[2])) # 140563140392256 print(id(v2[2])) # 140563140535744
-
python函数思维导图:
清晰版请下载:python基础-思维导图-函数