—— 小 峰 编 程
导 语:
从今天开始,我们将进入第二模块的学习——函数。第一模块主要是学习python基础知识,从第二模块开始就可以通过程序去解决工作中实际的问题。从今天开始,我们将进入第二模块的学习,此模块主要是:
函数,一个用于专门实现某个功能的代码块(可重用)。
1、内置函数
len、bin、oct、hex 等
2、自定义函数
def send_email():
# 实现了发送邮件的代码
pass
send_email()
3、模块,集成了很多功能的函数集合
内置模块,Python内部帮助我们提供好的。
import random num = random.randint(0,19)
import decimal v1 = decimal.Decimal("0.1") v2 = decimal.Decimal("0.2") v3 = v1 + v2 print(v3) # 0.3
第三方模块,网上下载别人写好的模块(功能集合)。
自定义模块
一. 文件操作
在学习文件操作之前,先来回顾一下编码的相关以及先关数据类型的知识。
字符串类型(str),在程序中用于表示文字信息,本质上是unicode编码中的二进制。
name = "武沛齐"
字节类型(bytes)
可表示文字信息,本质上是utf-8/gbk等编码的二进制(对unicode进行压缩,方便文件存储和网络传输。)
name="小峰" print(name.encode('utf-8')) data=b'\xe5\xb0\x8f\xe5\xb3\xb0' print(data.decode('utf-8'))#小峰
可表示原始二进制(图片、文件等信息)
# - 路径:
# 相对路径:'info.txt'
# 绝对路径:'/Users/PycharmProjects/luffyCourse/day09/info.txt'
# - 模式
# rb,表示读取文件原始的二进制(r, 读 read;b, 二进制 binary;)
#
在这里对于文件的文件打开的模式我们就不再一一介绍了, 主要参考:
https://docs.python.org/3/library/functions.html#filemodes
模式 介绍 ‘r’ 读取 ‘w’ 清空文件内容,然后写入 ‘x’ 新建文件,然后写入(如果文件存在,那直接失败) ‘a’ 在文件末尾写入,文件不存在就新建 ‘b’ 二进制模式 ‘t’ 文本模式 ‘+’ 更新(读和写) Python里面默认的模式是:'rt'。只要有'+',就变成了可读可写的;可以与w,r,a等进行组合。
1. 读文件
读文本文件
file_object = open('info.txt', 'rt',encoding='utf-8') data=file_object.read() print(data) file_object.close()
读图片等非文本内容文件。
file_object = open('my.png', 'rb') data = file_object.read() file_object.close() print(data)
注意事项:路径相关问题。注意区分相对路径与绝对路径。
windows系统中写绝对路径容易出问题:
#注意路径相关的问题 错误写法:file_object = open("D:\python\try1\info.txt","rt") 正确写法:file_object = open("D:\\python\\try1\\info.txt","rt") 正确写法:file_object = open(r"D:\python\try1\info.txt","rt")
读文件时,文件不存在程序会报错。所以一般在项目中我们会将其和os模块结合起来使用,判断一个路径是否存在。
file_path=r"D:\python\try1\info.txt" exists=os.path.exists(file_path) if not exists: print("文件不存在") else: file_object = open(file_path, "rt",encoding="utf-8") data = file_object.read() print(data) file_object.close()
2.写文件
写文本文件
# 路径:t1.txt # 模式:wb(要求写入的内容需要是字节类型) 1、打开文件 file_object = open(“info.txt”, mode=‘wb’) # 2.写入内容 file_object.write( “小峰”.encode(“utf-8”) ) # 3.文件关闭 file_object.close()
写图片非文本文件等文件
f1 = open('a1.png',mode='rb') content = f1.read() f1.close() f2 = open('a2.png',mode='wb') f2.write(content) f2.close()
基础案例:
#用户注册
# w写入文件,先清空文件;再在文件中写入内容。
file_object = open("files/info.txt", mode='wt', encoding='utf-8')
while True:
user = input("请输入用户名:")
if user.upper() == "Q":
break
pwd = input("请输入密码:")
data = "{}-{}\n".format(user, pwd)
file_object.write(data)
file_object.close()
补充:上下文书写格式,避免忘记关闭文件
with open('info.txt', mode='rb') as f:
data = f.read()
print(data)
二. 初识函数
函数,可以当做是一大堆功能代码的集合。
def 函数名():
函数内编写代码
...
函数名()
例如:
# 定义名字叫info的函数
def info():
print("第一行")
print("第二行")
print("第n行...")
info()
一般在项目开发中有会有两种应用场景:
有重复代码,用函数增加代码的重用性。
def send_email(): # 10行代码 print("欢迎使用计算机监控系统") if CPU > 90%: send_email() if 硬盘使用率 > 99%: send_email() if 内存使用率 > 98%: send_email() ...
代码太长,用函数增强代码的可读性。
def mycard(): card_color_list=["红桃","方片","梅花","方片"] card_num=["1","2","3","4","5","6","7","8","9","J","Q","K","A"] card=[f"{color}{num}"for color in card_color_list for num in card_num] card.append("大王") card.append("小王") return card cout=0 for i in mycard(): print(i) cout+=1 print(cout)
以前我们变成是按照业务逻辑从上到下逐步完成,称为:面向过程编程;现在学了函数之后,利用函数编程称为:函数式编程。
1. 函数的参数
之下面就来教大家用python发邮件,以下是我为大家提供的发邮件的一个函数。
import smtplib
from email.mime.text import MIMEText
from email.utils import formataddr
def send_email(email):
# ### 1.邮件内容配置 ###
# 邮件文本
msg = MIMEText("在吗", 'html', 'utf-8')
# 邮件上显示的发件人
msg['From'] = formataddr(["小峰", "wptawy@126.com"])
# 邮件上显示的主题
msg['Subject'] = "邮件主题"
# ### 2.发送邮件 ###
server = smtplib.SMTP_SSL("smtp.126.com")
server.login("wptawy@126.com", "WIYSAILOVUKPQGHY")
server.sendmail("wptawy@126.com", email, msg.as_string())
server.quit()
v3 = "xiaofeng@live.com"
send_email(v3)
1.1 参数
在定义函数时,如果在括号中添加变量
,我们称它为函数的形式参数:
#定义有三个参数的函数(a1/a2/a3一般称为形式参数-形参)
def func(a1,a2,a3):
print(a1+a2+a3)
# 执行函数并传入参数(执行函数传值时一般称为实际参数-实参)
func(11,22,33)
# 执行函数并传入参数
func(9,2,103)
位置传参
def add(n1,n2): print(n1+n2) add(1,22)
关键字传参:只能从右往左
def add(n1,n2): print(n1+n2) add(n1=1,n2=22)
1.2 默认参数
def func(a1, a2, a3=10):
print(a1 + a2 + a3)
# 位置传参
func(8, 19)
func(1, 2, 99)
# 关键字传参(位置和关键混合时,关键字传参要在后面)
func(12, 9, a3=90)
func(12, a2=9, a3=90)
func(a1=12, a2=9, a3=90)
1.3 动态参数
动态参数,定义函数时在形参位置用 *或**
可以接任意个参数。在定义函数时可以用 *和**
,其实在执行函数时,也可以用。
- *
def func(*args): print(args) # 元组类型 (22,) (22,33,99,) () # 只能按照位置传参 func(22) func(22,33) func(22,33,99) func()
**
def func(**kwargs): print(kwargs) # 字典类型 {"n1":"小峰"} {"n1":"小峰","age":"18","email":"xxxx"} {} # 只能按关键字传参 func(n1="小峰") func(n1="小峰",age=18) func(n1="小峰",age=18,email="xx@live.com")
*和**
def fun(*args,**kwargs): print(args) print(kwargs) fun(1,2,3,4,5) fun(13456,456,name="xiaofeng",age=19,sex="boy")
提示:是否还记得字符串格式化时的format功能。
v1 = "我叫{},今年{},性别{}".format("小峰",18,"男") v2 = "我叫{name},今年{age},性别{gender}".format(name="小峰",age=18,gender="男") print(v1) print(v2)
注意事项:
# 1. ** 必须放在 * 的后面 def func1(*args, **kwargs): print(args, kwargs) func1(1,2,name='John') # 2. 参数和动态参数混合时,动态参数只能放在最后。 def func2(a1, a2, a3, *args, **kwargs): print(a1, a2, a3, args, kwargs) func2(1,2,4,6,name='John',a=2,b=3,c=4) # 3. 默认值参数和动态参数同时存在 def func3(a1, a2, a3, a4=10, *args, a5=20, **kwargs): print(a1, a2, a3, a4, a5, args, kwargs) func3(11, 22, 33, 44, 55, 66, 77, a5=10, a10=123)
形参固定,实参用
*和**
def func(a1,a2): print(a1,a2) func( 11, 22 ) func( a1=1, a2=2 ) func( *[11,22] ) func( **{"a1":11,"a2":22} )
形参用
*和**
,实参也用*和**
def func(*args, **kwargs): print(args, kwargs) func(11, 22) func(11, 22, name="小峰", age=18) # 小坑,([11,22,33], {"k1":1,"k2":2}), {} func([11, 22, 33], {"k1": 1, "k2": 2}) # args=(11,22,33),kwargs={"k1":1,"k2":2} func(*[11, 22, 33], **{"k1": 1, "k2": 2}) # 值得注意:按照这个方式将数据传递给args和kwargs时,数据是会重新拷贝一份的 # (可理解为内部循环每个元素并设置到args和kwargs中)。
所以,在使用format字符串格式化时,可以可以这样:
v3 = "我是{},年龄:{}。".format(*["小峰",18]) v4 = "我是{name},年龄:{age}。".format(**{"name":"小峰","age":18}) print(v3) print(v4)
2. 函数返回值
在开发过程中,我们希望函数可以帮助我们实现某个功能,但让函数实现某功能之后有时也需要有一些结果需要反馈给我们,例如:
def func():
return 6666
print(func())
def func(a):
return a+10
print(func(2))
返回值可以是任意类型,如果函数中没写return,则默认返回None
def func(): value = 1 + 1 ret = func() print(ret) # None
当在函数中
未写返回值
或return
或return None
,执行函数获取的返回值都是None。def func(): value = 1 + 1 return # 或 return None ret = func() print(ret) # None
return后面的值如果有逗号,则默认会将返回值转换成元组再返回。
def func(): return 1,3,5,7 print(func(),type(func()))
函数一旦遇到return就会立即退出函数(终止函数中的所有代码)
def func(): print("开始") for i in range(1,10): if i%2==0: print(i) else: return print("结束") func() # 输出 开始
扩展:密码都不是明文。
- 注册京东,京东存储:用户名和密码(密文)
- 登录京东:用户名- 密码。
3.参数的补充
在函数基础部分,我们掌握函数和参数基础知识,掌握这些其实完全就可以进行项目的开发。补充内容包含:内存地址相关、面试题相关等,在特定情况下也可以让代码更加简洁,提升开发效率。
3.1 参数内存地址相关【面试题】
在开始开始讲参数内存地址相关之前,我们得先了解:使用 id查看下某个值的在内存中的地址
#查看某个值在内存中的地址
v='小峰'
print(id(v))
注:函数执行传参时,传递的是内存地址。
def func(data):
print(data,id(data))
v='小峰'
print(id(v))
func(v)
Python参数的这一特性有两个好处:
节省内存
对于可变类型且函数中修改元素的内容,所有的地方都会修改。可变类型:列表、字典、集合。
def fun1(data): data.insert(2,0) print(id(data)) v1=[1,2,3,4] #列表 fun1(v1) print(v1,id(v1)) ############################ def fun2(data): data.add(8) print(id(data)) v2={1,2,3,4}#集合 fun2(v2) print(v2,id(v2)) ########################### def fun3(data): data.update({"k4": 8}) print(id(data)) v3={"k1":1,"k2":2,"k3":3} #字典类型 fun3(v3) print(v3,id(v3))
提示注意:其他很多编程语言执行函数时,默认传参时会将数据重新拷贝一份,会浪费内存。.其他语言也可以通过 ref 等关键字来实现传递内存地址。当然,如果你不想让外部的变量和函数内部参数的变量一致,也可以选择将外部值拷贝一份,再传给函数。
def fun(data):
print(data,id(data))
v=[1,2,3,4,5]
print(v,id(v))
new_v=copy.deepcopy(v)
fun(new_v)
print(new_v,id(new_v))
#结果:
[1, 2, 3, 4, 5] 2213990555712
[1, 2, 3, 4, 5] 2213990545664
[1, 2, 3, 4, 5] 2213990545664
3.2 函数的返回值是内存地址
def func():
data = [11, 22, 33]
return data
v1 = func()
print(v1) # [11,22,33]
上述代码的执行过程:
- 执行func函数
data = [11, 22, 33]
创建一块内存区域,内部存储[11,22,33]
,data变量指向这块内存地址。return data
返回data指向的内存地址- v1接收返回值,所以 v1 和 data 都指向
[11,22,33]
的内存地址(两个变量指向此内存,引用计数器为2)- 由函数执行完毕之后,函数内部的变量都会被释放。(即:删除data变量,内存地址的引用计数器-1)
所以,最终v1指向的函数内部创建的那块内存地址。
3.3 参数的默认值【面试题】
这个知识点在面试题中出现的概率比较高,但真正实际开发中用的比较少。
def func(a1,a2=18):
print(a1,a2)
> 原理:
Python在创建函数(未执行)时,如果发现函数的参数中有默认值,则在函数内部会创建一块区域并维护这个默认值。 执行函数未传值时,则让a2指向 函数维护的那个值的地址。执行函数传值时,则让a2指向新传入的值的地址。在特定情况【默认参数的值是可变类型 list/dict/set】 & 【函数内部会修改这个值】下,参数的默认值 有坑 。
坑
# 在函数内存中会维护一块区域存储 def fun(a,b=[1,4,8]): b.append(100) print(a,b) print(id(a),id(b)) fun(1) fun(2) fun(3,[1,2,3,4,5]) 输出结果 1 [1, 4, 8, 100] 1555535456560 1555542237248 2 [1, 4, 8, 100, 100] 1555535456592 1555542237248 3 [1, 2, 3, 4, 5, 100] 1555535456624 1555542227200
大坑
# 在内部会维护一块区域存储 [1, 2, 10, 20,40 ] ,内存地址 1010101010 def func(a1, a2=[1, 2]): a2.append(a1) print(id(a2)) return a2 # a1=10 # a2 -> 1010101010 # v1 -> 1010101010 v1 = func(10) print(v1,id(v1)) # [1, 2, 10] # a1=20 # a2 -> 1010101010 # v2 -> 1010101010 v2 = func(20) print(v2,id(v2)) # [1, 2, 10, 20 ] # a1=30 # a2 -> 11111111111 [11, 22,30] # v3 -> 11111111111 v3 = func(30, [11, 22]) print(v3,id(v3)) # [11, 22,30] # a1=40 # a2 -> 1010101010 # v4 -> 1010101010 v4 = func(40) print(v4,id(v4)) # [1, 2, 10, 20,40 ]
深坑
# 内存中创建空间存储 [1, 2, 10, 20, 40] 地址:1010101010 def func(a1, a2=[1, 2]): a2.append(a1) return a2 # a1=10 # a2 -> 1010101010 # v1 -> 1010101010 v1 = func(10) # a1=20 # a2 -> 1010101010 # v2 -> 1010101010 v2 = func(20) # a1=30 # a2 -> 11111111111 [11,22,30] # v3 -> 11111111111 v3 = func(30, [11, 22]) # a1=40 # a2 -> 1010101010 # v4 -> 1010101010 v4 = func(40) 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]
4. 函数和函数名
函数名其实就是一个变量,这个变量只不过代指的函数而已。
注意:函数必须先定义才能被调用执行(解释型语言)。
# 正确
def add(n1,n2):
return n1 + n2
ret = add(1,2)
print(ret)
4.1 函数做元素
既然函数就相当于是一个变量,那么在列表等元素中是否可以把行数当做元素呢?
def func():
return 123
data_list = ["小峰", "func", func , func() ]
print( data_list[0] ) # 字符串"小峰"
print( data_list[1] ) # 字符串 "func"
print( data_list[2] ) # 函数 func
print( data_list[3] ) # 整数 123
res = data_list[2]()
print( res ) # 执行函数 func,并获取返回值;print再输出返回值。
print( data_list[2]() ) # 123
注意:函数同时也可被哈希,所以函数名通知也可以当做 集合的元素、字典的键。
掌握这个知识之后,对后续的项目开发有很大的帮助,例如,在项目中遇到根据选择做不同操作时:
情景1,例如:要开发一个类似于微信的功能。
def send_message(): """发送消息""" pass def send_image(): """发送图片""" pass def send_emoji(): """发送表情""" pass def send_file(): """发送文件""" pass def xxx(): """收藏""" pass function_dict = { "1": send_message, "2": send_image, "3": send_emoji, "4": send_file, "5": xxx } print("欢迎使用xx系统") print("请选择:1.发送消息;2.发送图片;3.发送表情;4.发送文件") choice = input("输入选择的序号") # "1" func = function_dict.get(choice) if not func: print("输入错误") else: # 执行函数 func()
情景2,例如:某个特定情况,要实现发送短信、微信、邮件。
def send_msg(): """发送短信""" pass def send_email(): """发送图片""" pass def send_wechat(): """发送微信""" pass func_list = [ send_msg, send_email, send_wechat ] for item in func_list: item()
上述两种情景,在参数相同时才可用,如果参数不一致,会出错。所以,在项目设计时就要让程序满足这一点,如果无法满足,也可以通过其他手段时间,例如:
情景:
def send_message(phone,content): """发送消息""" pass def send_image(img_path, content): """发送图片""" pass def send_emoji(emoji): """发送表情""" pass def send_file(path): """发送文件""" pass function_dict = { "1": [ send_message, ['15131255089', '你好呀']], "2": [ send_image, ['xxx/xxx/xx.png', '消息内容']], "3": [ send_emoji, ["😁"]], "4": [ send_file, ['xx.zip'] ] } print("欢迎使用xx系统") print("请选择:1.发送消息;2.发送图片;3.发送表情;4.发送文件") choice = input("输入选择的序号:") # 1 item = function_dict.get(choice) # [ send_message, ['15131255089', '你好呀']], if not item: print("输入错误") else: # 执行函数 func = item[0] # send_message param_list = item[1] # ['15131255089', '你好呀'] func(*param_list) # send_message(*['15131255089', '你好呀'])
4.2 函数名赋值
将函数名赋值给其他变量,函数名其实就个变量,代指某函数;如果将函数名赋值给另外一个变量,则此变量也会代指该函数,例如:
def func(a1,a2): print(a1,a2) new_fuc = func # 此时,new_fuc和func都代指上面的那个函数,所以都可以被执行。 func(1,1) new_fuc(2,2)
对函数名重新赋值,如果将函数名修改为其他值,函数名便不再代指函数,例如:
def func(a1,a2): print(a1,a2) # 执行func函数 func(11,22) # func重新赋值成一个字符串 func = "小峰" print(func)
注意:由于函数名被重新定义之后,就会变量新被定义的值,所以大家在自定义函数时,不要与python内置的函数同名,否则会覆盖内置函数的功能。
4.3 函数名做参数和返回值
函数名其实就一个变量,代指某个函数,所以和其他的数据类型一样,也可以当做函数的参数和返回值。
参数
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)
返回值
def plus(num): return num + 100 def handler(): print("执行handler函数") return plus result = handler() data = result(20) # 120 print(data)
5. 作用域
作用域,可以理解为一块空间,这块空间的数据是可以共享的。通俗点来说,作用域就类似于一个房子,房子中的东西归里面的所有人共享,其他房子的人无法获取。
5.1 函数为作用域
Python以函数为作用域,所以在函数内创建的所有数据,可以此函数中被使用,无法在其他函数中被使用。
def func():
age = 20#局部变量,局部变量优先原则
print(age)
def handler():
print(age)
age=5#全局变量
func()
handler()
print(age)
5.2 全局和局部
Python中以函数为作用域,函数的作用域其实是一个局部作用域。
# 全局变量(变量名大写)
COUNTRY = "中国"
CITY_LIST = ["北京","上海","深圳"]
def download():
# 局部变量
url = "http://www.xxx.com"
COUNTRY
和CITY_LIST
是在全局作用域中,全局作用域中创建的变量称之为【全局变量】,可以在全局作用域中被使用,也可以在其局部作用域中被使用。download
函数内部维护的就是一个局部作用域,在各自函数内部创建变量称之为【局部变量】,且局部变量只能在此作用域中被使用。局部作用域中想使用某个变量时,寻找的顺序为:优先在局部作用域中寻找,如果没有则去上级作用域中寻找
。(局部变量优先的原则)注意:全局变量一般都是大写。
示例1:在局部作用域中读取全局作用域的变量。
COUNTRY = "中国"
CITY_LIST = ["北京", "上海", "深圳"]
def download():
url = "http://www.xxx.com"
print(url)
print(COUNTRY)
print(CITY_LIST)
print(COUNTRY)
download()
示例2:局部作用域和全局作用域变量同名,局部变量优先
CITY_LIST = ["北京", "上海", "深圳"]
def download():
url = "http://www.xxx.com"
CITY_LIST = ["河北", "河南", "山西"]
print(url)
print(CITY_LIST)
print(CITY_LIST)
download()
5.3 global关键字
默认情况下,在局部作用域对全局变量只能进行:读取和修改内部元素(可变类型),无法对全局变量进行重新赋值。
读取
a=2 def fun(): print(a) #对全局变量的读取 fun()
修改内部元素(可变类型)
a=[2,"xiaofeng",(1,5)] print(a) def fun(): a.append("liu") #对内部元素的修改 a[2]=3 fun() print(a)
无法对全局变量重新赋值
a=2 def fun(): a=9 print(a) fun() #9 print(a) #2
如果想要在局部作用域中对全局变量重新赋值,则可以基于 global
关键字实现,例如:
a=2
def fun():
global a
a=9
print(a)
print(a)
fun()
print(a)
三、函数的进阶
- 函数的嵌套
- 闭包
- 装饰器
上述内容均属于函数部分必备知识,以后开发时直接和间接都会使用,请务必理解(重在理解,不要去死记硬背)。
1. 函数嵌套
Python中以函数为作用域,在作用域中定义的相关数据只能被当前作用域或子作用域使用。
1.1 函数在作用域中
其实,函数也是定义在作用域中的数据,在执行函数时候,也同样遵循:优先在自己作用域中寻找,没有则向上一接作用域寻找,例如:
# 1. 在全局作用域定义了函数func
def func():
print("你好")
# 2. 在全局作用域找到func函数并执行。
func()
# 3.在全局作用域定义了execute函数
def execute():
print("开始")
# 优先在当前函数作用域找func函数,没有则向上级作用域中寻找。
func()
print("结束")
# 4.在全局作用域执行execute函数
execute()
1.2 函数定义的位置
函数不仅可以定义在全局作用域中,其实函数也可以定义在局部作用域,这样函数被局部作用域和其子作用于中调用(函数的嵌套)。
def func(a):
print("开始")
def sum(a):
return a+2
def sub(a):
return a-2
if a>3:
return sum(a)
else:
return sub(a)
print(func(2))
print(func(4))
到现在你会发现,只要理解数据定义时所存在的作用域,并根据从上到下代码执行过程进行分析,再怎么嵌套都可以搞定。有没有想过为什么要这么嵌套定义?把函数都定义在全局不好吗?
其实,大多数情况下我们都会将函数定义在全局,不会嵌套着定义函数。不过,当我们定义一个函数去实现某功能,想要将内部功能拆分成N个函数,又担心这个N个函数放在全局会与其他函数名冲突时(尤其多人协同开发)可以选择使用函数的嵌套。
def f1():
pass
def f2():
pass
def func():
f1()
f2()
1.3 嵌套引发的作用域问题
基于内存和执行过程分析作用域。
name = "xiaofeng"
def run():
name = "alex"
def inner():
print(name)
def test():
print("hello")
return [inner, test]
func_list = run()
func_list[0]()
func_list[1]()
三句话搞定作用域:
- 优先在自己的作用域找,自己没有就去上级作用域。
- 在作用域中寻找值时,要确保此次此刻值是什么。
- 分析函数的执行,并确定函数
作用域链
。(函数嵌套)
2.闭包
闭包,简而言之就是将数据封装在一个包(区域)中,使用时再去里面取。(本质上闭包是基于函数嵌套搞出来一个中特殊嵌套)
闭包应用场景1:封装数据防止污染全局。
def func(age): name = "xiaofeng" def f1(): print(name, age) def f2(): print(name, age) f1() f2() func(123)
闭包应用场景2:封装数据封到一个包里,使用时在取。
def task(src): def inner(): print(src) return inner task('hello')()
3.装饰器
现在给你一个函数,在不修改函数源码的前提下,实现在函数执行前和执行后分别输入 “before” 和 “after”。
def func():
print("我是func函数")
value = (11,22,33,44)
return value
result = func()
print(result)
3.1 装饰器的引入
- 一般刚开始学编程的实现思路:
def func():
print("before")
print("我是func函数")
value = (11,22,33,44)
print("after")
return value
result = func()
- 更高级的的实现思路:
def func():
print("我是func函数")
value = (11, 22, 33, 44)
return value
def outer(origin):
def inner():
print('inner')
origin()
print("after")
return inner
func = outer(func)
result = func()
- 再对其进行优化后的思路,即就是处理返回值:
def func():
print("我是func函数")
value = (11, 22, 33, 44)
return value
def outer(origin):
def inner():
print('inner')
res = origin()
print("after")
return res #保持和原函数一样的效果
return inner
result = outer(func)()
3.2 装饰器的基本语法结构
在Python中有个一个特殊的语法糖,即就是本讲所说的装饰器:
def outer(origin):
def inner():
print('before')
res = origin()
print("after")
return res
return inner
@outer
def func():
print("我是func函数")
value = (11, 22, 33, 44)
return value
func()
3.3 装饰器在多函数中的应用
请在这3个函数执行前和执行后分别输入 “before” 和 “after”
def func1():
print("我是func1函数")
value = (11, 22, 33, 44)
return value
def func2():
print("我是func2函数")
value = (11, 22, 33, 44)
return value
def func3():
print("我是func3函数")
value = (11, 22, 33, 44)
return value
- 学完装饰器的实现思路:
def outer(org):
def inner():
print("before")
res = org()
print("after")
return res
return inner
@outer
def func1():
print("我是func1函数")
value = (11, 22, 33, 44)
return value
@outer
def func2():
print("我是func2函数")
value = (11, 22, 33, 44)
return value
@outer
def func3():
print("我是func3函数")
value = (11, 22, 33, 44)
return value
func1()
func2()
func3()
装饰器,在不修改原函数内容的前提下,通过@函数可以实现在函数前后自定义执行一些功能(批量操作会更有意义)。
3.4 装饰器的再优化,语法结构的改良
优化以支持多个参数的情况。
def outer(origin):
def inner(*args, **kwargs):
print("before 110")
res = origin(*args, **kwargs) # 调用原来的func函数
print("after")
return res
return inner
@outer # func1 = outer(func1)
def func1(a1):
print("我是func1函数")
value = (11, 22, 33, 44)
return value
@outer # func2 = outer(func2)
def func2(a1, a2):
print("我是func2函数")
value = (11, 22, 33, 44)
return value
func1(1)
func2(11, a2=22)
实现原理:基于@语法和函数闭包,将原函数封装在闭包中,然后将函数赋值为一个新的函数(内层函数),执行函数时再在内层函数中执行闭包中的原函数。
实现效果:可以在不改变原函数内部代码 和 调用方式的前提下,实现在函数执行和执行扩展功能。
适用场景:多个函数系统统一在执行前后自定义一些功能。
3.5 重要补充
装饰器实际上就是将原函数更改为其他的函数,然后再此函数中再去调用原函数。其实,一般情况下大家不用 functools 也可以实现装饰器的基本功能,但后期在项目开发时,不加 functools 会出错( 内部会读取__name__
,且__name__
重名的话就报错 ),所以在此大家就要规范起来自己的写法。 functools是 Python 标准库中的一个模块,提供了对高阶函数(即操作或返回其他函数的函数)的支持。它包含了一些常用的函数装饰器和工具,用于简化函数操作和优化代码。
- 获取函数名
def test():
pass
print(test.__name__) # test
def auth(func):
def inner(*args, **kwargs):
return func(*args, **kwargs)
return inner
@auth
def test():
pass
test()
print(test.__name__) # inner
import functools
def auth(func):
@functools.wraps(func)
def inner(*args, **kwargs):
return func(*args, **kwargs)
return inner
@auth
def handler():
pass
handler()
print(handler.__name__) # handler
def auth(func):
@functools.wraps(func)
def inner(*args, **kwargs):
"""巴巴里吧"""
res = func(*args, **kwargs) # 执行原函数
print(args)
print(kwargs)
return res
return inner
@auth
def func(*args, **kwargs):
pass
print(func(1,2,name="xiaofeng"))
print(func.__name__)
4. 匿名函数
传统的函数的定义包括了:函数名 + 函数体。匿名函数,则是基于lambda表达式实现定义一个可以没有名字的函数,在编写匿名函数时,由于受限 函数体只能写一行,所以匿名函数只能处理非常简单的功能。基于Lambda定义的函数格式为:lambda 参数:函数体
例如:
data_list = [ lambda x:x+100, lambda x:x+110, lambda x:x+120 ]
print( data_list[0] )
参数,支持任意参数。
lambda x: 函数体 lambda x1,x2: 函数体 lambda *args, **kwargs: 函数体
函数体,只能支持单行的代码。
返回值,默认将函数体单行代码执行的结果返回给函数的执行处。
func = lambda x: x + 100 v1 = func(10) print(v1) # 110
注:匿名函数适用于简单的业务处理,可以快速并简单的创建函数。
补充:三元运算
简单的函数,可以基于lambda表达式实现。简单的条件语句,可以基于三元运算实现
# 结果 = 条件成立时 if 条件 else 不成立
例如:
num=int(input("Enter a number:"))
print("hello") if num==1 else print("goodbye")
lambda表达式和三元运算没有任何关系,属于两个独立的知识点。但是掌握三元运算之后,以后再编写匿名函数时,就可以处理再稍微复杂点的情况了,例如:
#匿名函数,lambda结合三元运算就可以实现比较复杂的功能 func = lambda x: "大了" if x > 66 else "小了" print(func(1)) # "小了" print(func(100)) # "大了"
5. 生成器
生成器是由 函数+yield关键字 创造出来的写法,在特定情况下,用他可以帮助我们节省内存。
生成器函数,函数中有yield存在时,这个函数就是生产生成器函数。
def func(): print(111) yield 1
生成器对象,执行生成器函数时,会返回一个生成器对象。
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) # 执行生成器函数func,返回的生成器对象。 # 注意:执行生成器函数时,函数内部代码不会执行。 结束或中途遇到return,程序提示StopIteration错误
生成器的特点是,记录在函数中的执行位置,下次执行next时,会从上一次的位置基础上再继续向下执行。
应用场景:
当以后需要我们在内存中创建很多数据时,可以想着用基于生成器来实现一点一点生成(用一点生产一点),以节省内存的开销。
扩展:send()
def func(): print(111) v1 = yield 1 print(v1) print(222) v2 = yield 2 print(v2) print(333) data = func() n1 = data.send(None) print(n1) n2 = data.send(666) print(n2) n3 = data.send(777)
send
方法的工作原理
- 生成器函数在遇到
yield
语句时会暂停执行,并返回yield
后面的值。- 调用
send
方法时,生成器会从上次暂停的地方继续执行,并将send
方法的参数作为yield
表达式的结果。- 生成器继续执行,直到遇到下一个
yield
语句或函数结束。注意事项
- 在第一次调用生成器时,不能直接使用
send
方法,因为生成器还没有执行到第一个yield
语句。通常需要先调用next
方法或send(None)
来启动生成器。- 如果生成器已经结束(即没有更多的
yield
语句),再调用send
方法会抛出StopIteration
异常。
6.内置函数
Python内部为我们提供了很多方便的内置函数,在此整理出来36个给大家来讲解。
第1组
#内置函数 #abs 求绝对值 print(abs(-1)) #pow指数 print(pow(2,3)) #sum 求和 print(sum([1,4,9])) #divmod 求商和余数 print(divmod(7,4)) #min 求最小值 print(min([1,8,-2])) #max 求最大值 print(max([1,8,-2])) #any 是否存在为真 print(any([False,0,''])) #all 是否全为真 print(all([1,2,3])) #十进制转化为二进制 print(bin(10)) #十进制转化为八进制 print(oct(10)) #十进制转化为十六进制 print(hex(10)) # ord,获取字符对应的unicode码点(十进制) print(ord('A')) # chr,根据码点(十进制)获取对应字符 print(chr(ord('A')+1))
- 第2组,数据类型中有讲到
int、foat、str,unicode编码、bytes,utf-8、gbk编码、bool、list、dict、tuple、set
第3组(13个)
len、print、input、open、type,获取数据类型、range、id- 补充:
enumerate
#enumerate 枚举 #语法:enumerate(sequence, [start=0]) v1 = ["小峰", "alex", 'root'] for num, value in enumerate(v1, 1): print(num, value) # hash 它用于获取一个对象的哈希值。哈希值是一个固定长度的整数,用于表示一个对象的状态或标识,通常用于构建字典、集合等数据结构,以及进行数据加密等方面。 print(hash("xiaofeng")) #callable,是否可执行,后面是否可以加括号。 #通常用来判断是否为函数 def func(): print("Hello World") v=1 print(callable(func)) print(callable(v)) #zip # Python 3:zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的对象,这样做的好处是节约了不少的内存。我们可以使用 list() 转换来输出列表 v1=(1,5,8,3) v2=(5,71,1,0) v3=(20,4,6,9,39) print(zip(v1,v2,v3))#返回的是一个对象,最后一列不全,所以剔除掉 for i in zip(v1,v2,v3): print(i)
sorted,排序
#eg1: print(sorted([1,7,0,-2,7])) #由小到大的排序 print(sorted([1,7,0,-2,7],reverse=True)) #由大到小的排序 #eg2: 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]['age']) 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)
7.推导式
推导式是Python中提供了一个非常方便的功能,可以让我们通过一行代码实现创建list、dict、tuple、set 的同时初始化一些值。请创建一个列表,并在列表中初始化:0、1、2、3、4、5、6、7、8、9…299 整数元素。
列表
对列表的初始化 num_list=[i for i in range(10)] num_list=(i,i,i) for i in range(10) 结合三元运算对列表进行初始化 num_list=[i for i in range(10) if i%2==0]
集合
num_set1 = { i for i in range(10)} num_set2 = { (i,i,i) for i in range(10)} num_set3 = { (i,i,i) for i in range(10) if i%2==0 }
字典
num_dict1 = { i:i for i in range(10)} num_dict2 = { i:(i,11) for i in range(10)} num_dict3 = { 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)
看代码写结果(新浪微博面试题)
data_list = [lambda x: x + i for i in range(10)] # [函数,函数,函数] i=9 v1 = data_list[0](100) v2 = data_list[3](100) print(v1, v2) # 109 109
补充:
推导式支持嵌套
# 一副扑克牌 poker_list = [ (color,num) for num in range(1,14) for color in ["红桃", "黑桃", "方片", "梅花"]] print(poker_list)
烧脑面试题
def num(): return [lambda x: i * x for i in range(4)] # 1. num()并获取返回值 [函数,函数,函数,函数] i=3 ,即就是相同的函数和相同的i # 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)
本 篇 完 结 … …
持 续 更 新 中 … …