一、函数入门
什么是函数:
# 函数:函数是一系列代码的集,用来完成特定功能的代码块,类似于工具,可以重复不但的去使用
为什么要有函数:
# 优点:
# 1. 避免代码的冗余 # 2. 让程序代码结构更加清晰 # 3. 让代码具有复用性,便于维护
函数四部分:
1. 函数名:使用该函数的依据 2. 函数体:完成功能的代码块 3. 返回值:功能完成的反馈结果 4. 参数:完成功能需要的条件信息
定义函数的语法:
# 1、定义 # def 是声明函数的关键字,后面跟着函数名,括号内是函数的参数 def 函数名(参数1,参数2,参数3,...): '''注释''' 函数体 # 函数体写具体的逻辑代码 return 返回的值 # retrun后面是函数的返回值,是函数体代码的运行成果 # 2、调用 函数名(参数1,参数2,参数3,...)
根据函数体划分:
根据函数体划分: # 空函数:用来罗列功能 # 空函数指的是函数体用pass占位,pass代表什么也不做 def func(): pass # func():调用后什么是都不干 # 非空函数:正常可以完成某项功能 def func(): print('非空函数') # func():调用后执行函数体 根据参数列表划分: # 无参函数:不需外界资源 def start(): print('系统启动')
start() # 有参函数:需要外界资源 def login(usr, pwd): if usr == 'simon' and pwd == '123': print('登录通过') else: print('登录失败') # 根据返回值划分:return是用来结束函数的: # 空返回:返回None #没有返回值,默认返回None def demo(x, y): print( x + y )
#主动书写return,返回None def demo(x, y): print( x + y ) return # 一值返回 def demo(x, y): return x + y # 多值返回:结束函数且带出多个值: 本质就是带出一个元组 def demo(x, y): return x + y, x - y, x * y, x / y
demo(10,20)
# 结果:(30, -10, 200, 0.5)
如何定义函数:
# 1、功能的单一化 # 2、参数需要外界提供、通过函数的参数来实现 # 3、函数执行后的结果,通过返回值告诉给外界
二、函数的形参与实参
形参与实参
def fn(形参们): pass fn(实参们) # 形参:在函数定义时()里出现的参数 -- 形参本身没有实际值(意义),在函数调用时,传入什么实参,形参就装有什么值 # 实参:在函数调用时()里出现的参数 -- 实参有实际值(意义) # 重点:函数调用传参:将实参的值赋值给形参 | 形参要获取外界的值只能通过实参进行获取
实参分类
位置实参: 1.传参两种方式:实参名 | 实参具体值 2.必须按位置对形参进行传值 关键字实参: 1.传参两种方式:形参名=实参名 | 形参名=实参值 2.可以指名道姓对形参进行传值,所以可以不用按位置进行传参
def func(a, b, c):
print(a, b, c)
# func(10, b=20, 200) 报错:SyntaxError: positional argument follows keyword argument
# 重点:两种实参在一起进行传参时:必须位置在前,关键字在后
两大形参分类
形参种类: 1)位置形参 -- 普通位置形参 -- 默认值形参 -- 可变长位置形参 2)关键字形参 -- 有默认值关键字形参 -- 无默认值关键字形参 -- 可变长关键字形参
def fn(a, b, *, x, y): # 位置形参:a, b 关键字形参: x, y pass # 重点: 1.*为分水岭 2.位置实参只能给位置形参进行传值 3.关键字实参可以给位置形参与关键字形参进行传值
# 两个带默认值的形参 def fn2(a=10, *, x=20): print(a, x) fn2(100, x=200) # 总结: # 1.有默认值的参数可以不用传值 # 2.*前有默认值的叫默认值参数,属于位置形参,可以被位置及关键字实参进行传值 # 3.*后有默认值的叫有默认值的关键字形参,属于关键字形参,只能被关键字实参进行传值 # 4.如果省略*, 有默认值的形参都是默认值参数 # 不带默认值与带默认值形参结合使用 def fn3(a, b=10, *, x, y=20, z): print(a, b, x, y, z) fn3(100, x=200, z=300)
# 结果:
# 100 10 200 20 300
# 总结: # 1.没有默认值的必须传参,有默认值的可以传也可以不传 # 2.位置有值的必须出现在无值之后,关键字顺序不做要求
可变长形参与可变长关键字形参:
def fn4(a, b=10, *args, x, **kwargs): print(a, b, x) print(args) print(kwargs) fn4(10, 20, 30, x=100, y=200, z=300)
# 结果:
# 10 20 100
# (30,)
# {'y': 200, 'z': 300}
# 总结: # 1.可变长是用来接收未接收完的值(接收0到n个): # -- *args用来接收所有没有接收完的位置(只能接收位置实参) # -- **kwargs用来接收所有没有接收完的关键字(只能接收关键字实参) # 2.*args必须出现在所以位置参数之后,**kwargs必须出现在所以参数之后
# 假设第一个位置永远是参数name
def func4(*args, **kwargs):
name = args[0] # 将name抽出来
def func44(name, *args, **kwargs):
# name 可以直接接收,省了抽出来的过程
pass
可变形参打散传值:
def fn5(*args, **kwargs): print(args, kwargs) # args=(10, 20) kwargs={'x': 100, 'y': 200} fn5(10, 20, x=100, y=200) def fn55(*args, **kwargs): print(args, kwargs) # args=(10, 20) kwargs={'x': 100, 'y': 200} fn5(*args, **kwargs) # *args就会把元组打散传递,**kwargs就会把字典打散传递
#fn5(args, kwargs) # 结果:((10, 20), {'x': 100, 'y': 200}) {} # 不加*号,字典是空的,没有打散 fn55(10, 20, x=100, y=200) # 结果:(10, 20) {'x': 100, 'y': 200}
# 容器类型可以打散传值 def temp(*args, **kwargs): print(args, kwargs) ls = [1, 2, 3, 4, 5] dic = {'a': 1, 'b': 2} temp(*ls, **dic) # 结果:(1, 2, 3, 4, 5) {'a': 1, 'b': 2}
# 注:字符串也可以作为单列集合进行打散传递
fn(*'abc') # => ('a', 'b', 'c') {}
总结:
# 1.位置实参只能给位置形参传值 # 2.关键字实参可以给位置及关键字形参传值 # 3.有默认值的可以不用传参 # 4.可变长位置形参只能接受位置实参,接受位置形参没有接收完的位置实参,存放到元组中 # 5.可变长关键字形参只能接受关键字实参,接受关键字形参没有接收完的关键字实参,存放到字典中 # 6.*args必须出现在所有位置形参之后,**kwargs必须在所有形参之后
三、函数对象
什么是函数对象:
# 函数名就是存放了函数的内存地址,存放了内存地址的变量都是对象,即 函数名 就是 函数对象 # 函数对象:存放函数地址的变量就叫函数对象,就是函数名
函数对象的应用场景:
# 应用场景: # 1 可以直接被引用 # 2 可以当作函数参数传递 # 3 可以作为函数的返回值 # 4 可以作为容器类型的元素
举例:
def fn(): print(1) print(2) print(3) return 1 print(fn) # 结果:<function fn at 0x0000000001D02EA0> # 1.函数对象():拿到函数地址并执行 - 函数的调用 # 2.函数调用一定会得到一个结果 - 函数的返回值 - 函数值: res=fn() == 函数调用后res=1 res = fn() print(res) # 1 2 3 1 # res = 1 print(res) # 1 #调用后再打印的结果还是1 # 可以当做函数参数传值 def fn2(): print('fn2 run') # fn2() aaa = fn2 # 直接赋值 # aaa() def fn22(fn): # fn = aaa = fn2 # 作为参数 fn() fn22(aaa) #fn2 run print("-------------------------") def fn222(): # return fn2() return fn2 # 作为返回值 res = fn222() # res = fn2() = None | res = fn2 = 函数对象 print(res()) #fn2 run和None #可以作为容器类型 ls = [fn2, 10, 20] # 作为容器对象的成员 print(ls[1]) # 10 print(ls[0]()) #fn2 run和None # print(ls) #[<function fn2 at 0x00000000004C2EA0>, 10, 20]
# 功能体 def add(n1, n2): return n1 + n2 def low(n1, n2): return n1 - n2 def jump(n1, n2): return n1 * n2 # 完成功能 def computed(n1, n2, fn): # fn = add|low|jump res = fn(n1, n2) # 调用具体的功能 return res # 功能对应关系 method_map = { # 指令与函数对象的对应关系 '1': add, '2': low, '3': jump } # 获取功能 def get_method(cmd): if cmd in method_map: return method_map[cmd] # 返回 add|low|jump return add # 当指令错误,add作为默认功能 while True: cmd = input('cmd: ') res = get_method(cmd)(10, 20) # 根据指令获取功能并调用得到结果 print(res) """ #实验的过程 while True: cmd = input('cmd: ') res = 0 # if cmd in method_map: # # res = method_map[cmd](10, 20) # fn = get_method(cmd) # res = fn(10, 20) res = get_method(cmd)(10, 20) print(res) """
函数的嵌套调用
定义:函数的嵌套调用:在一个函数内部调用另一个函数
# 案例: # 求两个数最大值 def max_two(n1, n2): if n1 > n2: return n1 return n2 # 求三个数最大值:两两相比 def max_three(n1, n2, n3): max = max_two(n1, n2) return max_two(max, n3) # 求四个数最大值 def max_four(n1, n2, n3, n4): max = max_three(n1, n2, n3) return max_two(max, n4) print(max_four(20, 50, 30, 50)) #50
名称空间:
# 名称空间:存放名字与内存空间地址对应关系的容器 # 作用:解决由于名字有限,导致名字重复发送冲突的问题 - 内置全局局部可以同时使用一个名字存放不同地址 # 三种名称空间 # Built-in:内置名称空间;系统级,一个;随解释器执行而产生,解释器停止而销毁 # Global:全局名称空间;文件级,多个;随所属文件加载而产生,文件运行完毕而销毁 # Local:局部名称空间;函数级,多个;随所属函数执行而产生,函数执行完毕而销毁 # 加载顺序:Built-in > Global > Local # -- 采用堆栈存储数据的方式(压栈),导致内置最后被访问
# 注:
# del 名字:可以移除查找最近的名字与内存空间地址的对应关系
# 加载顺序:Built-in > Global > Local
函数的嵌套定义:
# 函数的嵌套定义:在函数内部定义函数 # 诞生的理由:一个函数想使用另一个函数内部的变量,可以定义在其内部 def func(): a = 10 def fn(): print(a) return fn new_fn = func() new_fn() # 10
# 作用域:变量的作用范围 len = 10 def fn3(): len = 100 def fn33(): len = 1000 print('1:', len) fn33() print('2:', len) fn3() print('3:', len) # 结果: # 1: 1000 # 2: 100 # 3: 10
# nonlocal关键字:统一局部与嵌套局部的变量名 def fn2(): num = 666 def fn22(): nonlocal num num = 888 fn22() print(num) fn2() # 888 # global:统一局部与全局的变量名 num = 10 def outer(): def inner(): global num # 让函数的局部变量num与全局变量num统一,两者是一个 num = 1000 #return inner()
inner() print(num) # 10 outer() # 函数执行后num变量就编程了全局变量 print(num) # 1000
作用域:
# 作用域:名字起作用的范围 # 作用:解决同名字可以共存问题 - 不同作用域相同名字的值都能在其作用域范围下进行使用 # 四种作用域 # Built-in:内置作用域 # Global:全局作用域 # Enclosing:嵌套作用域 # Local:局部作用域 # 注: # 不同作用域之间名字不冲突,以达到名字的重用 # 查找顺序:Local > Enclosing > Global > Built-in
# 加载顺序:Built-in > Global > Enclosing > Local
# 作用范围:Built-in > Global > Enclosing > Local
def add_b(): global b b = 42 def do_global(): #global b b = b + 10 print(b) do_global() print(b) add_b() # 报错:UnboundLocalError: local variable 'b' referenced before assignment # global 定义的 b ,只能引用,不能修改
函数的闭包:
# closure:闭包 # 闭包:定义在函数内部的函数,这个内部的函数就是闭包
# 完整的闭包结构:1.将函数进行闭包处理;2.提升函数名的作用域
# 应用场景: # 1.可以去使用其他函数的内部变量,且还可以保证调用位置不变(闭包的函数对象作为那个函数的返回值) def outer(): count = 3000 def fn(): print(count) # 能使用outer内部的变量count return fn # 还是在外界调用 outer()() # outer()() => fn() => 调用fn
# 延迟执行(外层函数可以为内存函数传递参数) import requests # def show_html(url): # response = requests.get(url) # print(response.text) # # show_html('https://www.baidu.com') # # show_html('https://www.baidu.com') # # show_html('https://www.sina.com.cn') def outer(url): def show_html(): response = requests.get(url) print(response.text) return show_html # 制作 爬百度与新浪的 函数对象 show_baidu = outer('https://www.baidu.com') show_sina = outer('https://www.sina.com.cn') # 延迟到需求来了,需要爬百度,就用百度函数对象,需要爬新浪,就用新浪函数对象 show_baidu() show_sina() show_baidu()
练习:
1、写函数,,用户传入修改的文件名,与要修改的内容,执行函数,完成批了修改操作 2、写函数,计算传入字符串中【数字】、【字母】、【空格] 以及 【其他】的个数 3、写函数,判断用户传入的对象(字符串、列表、元组)长度是否大于5。 4、写函数,检查传入列表的长度,如果大于2,那么仅保留前两个长度的内容,并将新内容返回给调用者。 5、写函数,检查获取传入列表或元组对象的所有奇数位索引对应的元素,并将其作为新列表返回给调用者。 6、写函数,检查字典的每一个value的长度,如果大于2,那么仅保留前两个长度的内容,并将新内容返回给调用者。 dic = {"k1": "v1v1", "k2": [11,22,33,44]} PS:字典中的value只能是字符串或列表
# 题一 import os def move_file(old_file,new_file,old,new): with open(old_file,'r',encoding='utf-8') as r,open(new_file,'w',encoding='utf-8') as w: for line in r.readlines(): if old in line: line = line.replace(old,new) w.write(line) os.remove(old_file) move_file(r'F:\安装包\JetBrains\JetBrains\Projects\04\代码\work\file',r"D:\\a.py",'sd','SD') # 题二 def check_str(msg): res={ 'num':0, 'string':0, 'space':0, 'other':0, } for s in msg: if s.isdigit(): res['num']+=1 elif s.isalpha(): res['string']+=1 elif s.isspace(): res['space']+=1 else: res['other']+=1 return res def NR(): with open('file','r',encoding='utf-8') as r: data = r.read() return data res=check_str(NR()) print(res) # {'num': 4, 'string': 24, 'space': 2, 'other': 2} #题目四 def func1(seq): if len(seq) > 2: seq=seq[0:2] return seq print(func1([1,2,3,4])) #题目五 def func2(seq): return seq[::2] print(func2([1,2,3,4,5,6,7])) #题目六 def func3(dic): d={} for k,v in dic.items(): if len(v) > 2: d[k]=v[0:2] return d print(func3({'k1':'abcdef','k2':[1,2,3,4],'k3':('a','b','c')}))
四、装饰器
什么是装饰器:
# 装饰器:就是闭包(闭包的一个应用场景) -- 把要被装饰的函数作为外层函数的参数通过闭包操作后返回一个替代版函数 # 优点: -- 丰富了原有函数的功能 -- 提高了程序的可拓展性
开放封闭原则:
# 开放封闭原则: # 开放:拓展功能的点是开放的 - 可以为之前的函数添加新功能 # 封闭:1.不能改变原函数的源代码 2.还有通过原函数的函数对象来调用函数
# 需求:如何拓展一个原有函数的功能 # -- 修改源代码 # -- 创建一个包含该功能和其他新功能的新函数
# 错误一:修改了源代码 # def huaping(): # print('插花功能') # print('观赏功能') # huaping() # 错误二:改变了调用方式 # def huaping(): # print('插花功能') # def my_huaping(fn): # fn() # print('观赏功能') # my_huaping(huaping)
# 装饰器:满足开放封闭原则还能拓展新功能
# 装饰器:满足开放封闭原则还能拓展新功能,简单 ''' def huaping(): print('插花功能') temp = huaping def my_huaping(): temp() print('观赏功能') huaping = my_huaping huaping() ''' # 装饰器演变一: ''' def huaping(): print('插花功能') def outer(fn): # fn = huaping # temp = huaping # 可以提取到实参对形参传递 def my_huaping(): fn() print('观赏功能') return my_huaping huaping = outer(huaping) # 要整合该条逻辑 huaping() # 装饰器演变二: def outer(fn): # fn = 原功能的huaping def my_huaping(): # my_huaping => 新功能的huaping fn() print('观赏功能') return my_huaping @outer # huaping = outer(huaping) 被装饰的函数对象 = 装饰器外层函数对象(被装饰的函数对象) def huaping(): print('插花功能') huaping() # 被装饰后的my_huaping
语法:
# 被装饰的函数可能有参有返:装饰器模板,可以满足所有参数,且能装饰原函数返回值 def outer(func): # temp = huaping def inner(*args, **kwargs): pass res = func(*args, **kwargs) pass return res return inner @outer def any_method(): pass
def outer(func): def inner(): print("新增功能1") func() print("新增功能2") return inner @outer def func(): print("原有功能")
装饰器有参有返的函数
def outer(func): def inner(*args, **kwargs): print("新增功能1") result = func(*args, **kwargs) print("新增功能2") return result return inner @outer def func(*args, **kwargs): print("原有功能") return "原有结果"
有参装饰器
def wrap(arg): def outer(func): def inner(*args, **kwargs): print("新增功能1") result = func(*args, **kwargs) print("新增功能2") return result return inner @wrap("装饰器参数") def func(*args, **kwargs): print("原有功能") return "原有结果"
举例:
# 为什么要出现带参装饰器 def outer(func): # outer与inner之间要使用外部数据 # 可以解决的方案路径,给outer添加参数,但是outer的参数是固定一个,就是被装饰的函数 def inner(*args, **kwargs): res = func(*args, **kwargs) return res return inner # 所以只能使用函数的闭包,通过外层函数给内存函数传递参数的方式 def wrap(*arg, **kwargs): def outer(func): # 就可以使用wrap中的*arg, **kwargs,就是要使用的外部数据
print(*arg, **kwargs)
def inner(*args, **kwargs): res = func(*args, **kwargs) return res return inner return outer a = 10 b = 20 @wrap(a, b) # @wrap(10, 20) => @outer => fn = outer(fn) => fn = inner def fn(): pass
from functools import wraps def outer(func): @wraps(func) def inner(*args, **kwargs): '''装饰器文档注释''' func(*args, **kwargs) return inner @outer def func(*args, **kwargs): '''原有文档注释''' print("原有功能")
from functools import wraps def outer(func): # @wraps(temp) # 将inner.__doc__指向temp的__doc__ @wraps(func) # 将inner.__doc__指向被装饰的func的__doc__ def inner(*args, **kwargs): ''' :param args: 被装饰函数的位置形参 :param kwargs: 被装饰函数的关键字形参 :return: ''' res = func(*args, **kwargs) return res return inner @outer def fn3(arg): ''' :param arg: fn3的参数 :return: ''' print(fn3) # fn3 == inner # fn3本质是inner,但是我打印文档注释,能不能形成一个打印假象, # 打印的是fn3自己文档注释 - 为了统一查看原码和打印文档注释,显示的是同样内容 print(fn3.__doc__)
fn.__doc__就是打印该函数的文档注释(解释该函数功能的)
#需求:验证账号密码登录;登录时检验账号密码是否合格并输出对应的提示 # 为登录功能添加账号检验功能:必须是3个及以上英文字母组成 def check_user(func): def inner(user, pwd): if not (user.isalpha() and len(user) >= 3): return '账号不合法' res = func(user, pwd) return res return inner # 为登录功能添加密码检验功能:必须是3个及以上英文字母或数字组成 def check_pwd(func): def inner(*args, **kwargs): pwd = args[1] if not (pwd.isalnum() and len(pwd) >= 3): return '密码不合法' res = func(*args, **kwargs) return res return inner # 对登录结果的修饰装饰器:True=>登录成功 False=>登录失败 def change_res(func): def inner(*args, **kwargs): res = func(*args, **kwargs) if res == True: return '登录成功' return '登录失败' return inner # 装饰器被执行的过程是从上至下 @check_user # login = check_user(func=login) = inner @check_pwd @change_res def login(user, pwd): # 被装饰的函数对象 if user == 'owen' and pwd == '123': return True return False user = input('user: ') pwd = input('pwd: ') res = login(user, pwd) print(res)
# 装饰器:固定写法 def outer(func): def inner(*args, **kwargs): pass res = func(*args, **kwargs) pass return res return inner @outer def f1(): # 任意函数 pass f1()