python 函数
函数
函数的优势:
1. 减少代码的重复性
2. 使代码可读性更好
函数的的结构
def 函数名(): 函数体
return 返回值
return
1.遇到return,函数结束,return下面的(函数内)的代码不会执行。
2.return 会给函数的执行者返回值。
如果return后面什么都不写,或者函数中没有return,则返回的结果是None
如果return后面写了一个值,返回给调用者这个值
如果return后面写了多个结果,,返回给调用者一个tuple(元组),调用者可以直接使用元组的解构获取多个变量。
三元运算符
c = a if a > b else b #当a>b就把a赋值给c,否则就把b赋值给c
函数的参数
函数的参数可以从两个角度划分:
1.形参
写在函数声明的位置的变量叫形参,形式上的一个完整.表示这个函数需要xxx(上面的是形参)
2.实参
在函数调用的时候给函数传递的值.加实参,实际执行的时候给函数传递的信息.表示给函数xxx
函数的传参就是函数将实际参数交给形式参数的过程.(下面的是实参)
实参角度
实参的⾓角度来看参数分为三种:
1. 位置参数
2. 关键字参数
3. 混合参数, 位置参数必须在关键字参数前面
1. 位置参数
位置参数就是从左至右,实参与形参一一对应。
2. 关键字参数
def date(sex, age, hobby): print("拿出手机") print("打开陌陌") print('设置筛选条件:性别: %s,年龄:%s,爱好:%s' %(sex, age, hobby)) print("找个漂亮的妹子") print("问她,约不约啊!") print("ok 走起") date(hobby='唱歌',sex='女',age='25~30',)
3. 混合参数
可以把上面两种参数混合着使用. 也就是说在调用函数的时候即可以给出位置参数, 也可以指定关键字参数.
混合参数一定要记住:关键字参数一定在位置参数后面。
形参角度
1. 位置参数
位置参数其实与实参角度的位置参数是一样的,就是按照位置从左至右,一一对应
2. 默认值参数
def func(name, age, sex='男'): print("录入学生信息") print(name, age, sex) print("录入完毕") func("张强", 18)
3.万能参数(动态参数)
动态参数分为两种:动态接受位置参数 *args,动态接收关键字参数**kwargs.
动态接收位置参数:*args
形参会将实参所有的位置参数接收,放置在一个元组中
动态接收关键字参数: *kwargs
形参接受所有的关键字参数然后将其转换成一个字典
* 的魔性用法
函数中分为打散和聚合。
函数外可以处理剩余的元素。
函数的打散和聚合
*起到的是打散
s1 = 'alex' l1 = [1, 2, 3, 4] tu1 = ('武sir', '太白', '女神',) def func(*args): print(args) # ('a', 'l', 'e', 'x', 1, 2, 3, 4, '武sir', '太白', '女神') func(*s1,*l1,*tu1)
**起到的是打散
dic1 = {'name': '太白', 'age': 18} dic2 = {'hobby': '喝茶', 'sex': '男'} def func(**kwargs): print(kwargs) # {'name': '太白', 'age': 18, 'hobby': '喝茶', 'sex': '男'} func(**dic1,**dic2)
4.形参的第四种参数:仅限关键字参数
def foo(a,b,*args,c,sex=None,**kwargs): print(a,b) print(c) print(sex) print(args) print(kwargs)
func(1,2,3,4,5,6,7,sex='女',name='Alex',age=80,c='666')
注意:必须先声明在位置参数,才能声明关键字参数
综上:在形参的角度来看
1. 位置参数
2. 默认认值参数(大多数传进来的参数都是一样的, 一般用默认参数)
3. 万能参数
4. 仅限关键字参数
形参角度的所有形参的最终顺序为:*位置参数,*args,默认参数,仅限关键字参数,*kwargs。
函数名的运用
1. 函数的内存地址
2. 函数名可以赋值给其他变量
3. 函数名可以当做容器类的元素
4. 函数名可以当做函数的参数
5. 函数名可以作为函数的返回值
小结:函数名是一个特殊的变量,他除了具有变量的功能,还有最主要一个特点就是加上() 就执行,其实他还有一个学名叫第一类对象。
可迭代对象
在python中,但凡内部含有__iter__方法的对象,都是可迭代对象。
常见的可迭代对象有:
str list tuple dic set range 文件句柄等
不是可迭代对象的是:
int,bool
小结
从字面意思来说:可迭代对象就是一个可以重复取值的实实在在的东西。
从专业角度来说:但凡内部含有__iter__方法的对象,都是可迭代对象。
可迭代对象可以通过判断该对象是否有’__iter__’方法来判断。
可迭代对象的优点:
可以直观的查看里面的数据。
可迭代对象的缺点:
1. 占用内存。
2. 可迭代对象不能迭代取值(除去索引,key以外)。
迭代器
1. 迭代器的定义
从字面意思来说迭代器,是一个可以迭代取值的工具,器:在这里当做工具比较合适。
在python中,内部含有'__Iter__'方法并且含有'__next__'方法的对象就是迭代器。
2. 迭代器
可迭代对象:str list tuple dict set range
迭代器:文件句柄
3. 可迭代对象如何转化成迭代器
l1 = [1, 2, 3, 4, 5, 6] obj = l1.__iter__() # 或者 iter(l1)print(obj) # <list_iterator object at 0x000002057FE1A3C8>
4.迭代器取值
迭代器是利用__next__()进行取值:
l1 = [1, 2, 3,] obj = l1.__iter__() # 或者 iter(l1) # print(obj) # <list_iterator object at 0x000002057FE1A3C8> ret = obj.__next__()
5. while模拟for的内部循环机制
l1 = [1, 2, 3, 4, 5, 6] # 1 将可迭代对象转化成迭代器 obj = iter(l1) # 2,利用while循环,next进行取值 while 1: # 3,利用异常处理终止循环 try: print(next(obj)) except StopIteration: break
小结
迭代器的优点:
节省内存。
迭代器在内存中相当于只占一个数据的空间:因为每次取值都上一条数据会在内存释放,加载当前的此条数据。
惰性机制。
next一次,取一个值,绝不过多取值。
迭代器的缺点:
不能直观的查看里面的数据。
取值时不走回头路,只能一直向下取值。
可迭代对象与迭代器对比
可迭代对象:
是一个私有的方法比较多,操作灵活(比如列表,字典的增删改查,字符串的常用操作方法等),比较直观,但是占用内存,而且不能直接通过循环迭代取值的这么一个数据集。
应用:当你侧重于对于数据可以灵活处理,并且内存空间足够,将数据集设置为可迭代对象是明确的选择。
迭代器:
是一个非常节省内存,可以记录取值位置,可以直接通过循环+next方法取值,但是不直观,操作方法比较单一的数据集。
应用:当你的数据量过大,大到足以撑爆你的内存或者你以节省内存为首选因素时,将数据集设置为迭代器是一个不错的选择。(可参考为什么python把文件句柄设置成迭代器)。
生成器
生成器是需要我们自己用python代码构建的工具
1. 生成器的构建方式
在python中有三种方式来创建生成器:
1. 通过生成器函数
2. 通过生成器推导式
3. python内置函数或者模块提供(其实1,3两种本质上差不多,都是通过函数的形式生成,只不过1是自己写的生成器函数,3是python提供的生成器函数而已)
2. 生成器函数
将函数中的return换成yield,这样func就不是函数了,而是一个生成器函数
def func(): print(11) yield 22 ret = func() print(ret) # 运行结果: <generator object func at 0x000001A575163888> # 获得生成器对象
生成器的本质就是迭代器.迭代器如何取值,生成器就如何取值。所以我们可以直接执行next()来执行以下生成器
def func(): print("111") yield 222 gener = func() # 这个时候函数不会执⾏. ⽽是获取到⽣成器 ret = gener.__next__() # 这个时候函数才会执⾏ print(ret) # 并且yield会将func生产出来的数据 222 给了 ret。 结果: 111 222
yield与return的区别:
return一般在函数中只设置一个,他的作用是终止函数,并且给函数的执行者返回值。
yield在生成器函数中可设置多个,他并不会终止函数,next会获取对应yield生成的元素。
yield 与 yield from的区别:
yield 有返回值 也可以接受返回值,yield是直接yield的是可迭代对象
yield from 调用生成器 ,yield from是将可迭代对象中的元素一个一个yield出来
推导式
li = [] for i in range(10): li.append(i) print(li)
推导式写法
ls = [i for i in range(10)] print(ls)
列表推导式分为两种模式:
1.循环模式:[变量(加工的变量) for 变量 in iterable]
2.筛选模式: [变量(加工的变量) for 变量 in iterable if 条件]
当然还有多层循环的,这个我们一会就会讲到,那么我们先来看循环模式。
循环模式
从python1期到python100期写入列表lst lst = [f'python{i}' % i for i in range(1,19)] print(lst)
筛选模式
筛选模式就是在上面的基础上加上一个判断条件,将满足条件的变量留到列表中。 过滤掉长度小于3的字符串列表,并将剩下的转换成大写字母 l = ['wusir', 'laonanhai', 'aa', 'b', 'taibai'] # print([i.upper() for i in l if len(i) > 3])
生成器表达式
生成器表达式和列表推导式的语法上一模一样,只是把[]换成()就行了
比如将十以内所有数的平方放到一个生成器表达式中 gen = (i**2 for i in range(10)) print(gen) # 结果: <generator object <genexpr> at 0x0000026046CAEBF8>
生成器表达式也可以进行筛选
# 获取1-100内能被3整除的数 gen = (i for i in range(1,100) if i % 3 == 0) for num in gen: print(num)
生成器表达式和列表推导式的区别:
1. 列表推导式比较耗内存,所有数据一次性加载到内存。而.生成器表达式遵循迭代器协议,逐个产生元素。
2. 得到的值不一样,列表推导式得到的是一个列表.生成器表达式获取的是一个生成器
3. 列表推导式一目了然,生成器表达式只是一个内存地址。
匿名函数
普通版
def func(a,b): return a+b print(func(3,4))
匿名函数
func = lambda a,b: a+b print(func(3, 4)) # 7
语法:
函数名 = lambda 参数:返回值
1)此函数不是没有名字,他是有名字的,他的名字就是你给其设置的变量,比如func.
2)lambda 是定义匿名函数的关键字,相当于函数的def.
3)lambda 后面直接加形参,形参加多少都可以,只要用逗号隔开就行。
func = lambda a,b,*args,sex= 'alex',c,**kwargs: kwargs print(func(3, 4,c=666,name='alex')) # {'name': 'alex'} # 所有类型的形参都可以加,但是一般使用匿名函数只是加位置参数,其他的用不到。
4)返回值在冒号之后设置,返回值和正常的函数一样,可以是任意数据类型。
5)匿名函数不管多复杂.只能写一行.且逻辑结束后直接返回数据
闭包
闭包的定义:
1. 闭包是嵌套在函数中的函数。
2. 闭包必须是内层函数对外层函数的变量(非全局变量)的引用。
闭包的作用:保存局部信息不被销毁,保证数据的安全性。
闭包的应用:
-
可以保存一些非全局变量但是不易被销毁、改变的数据。
-
装饰器。
装饰器
开放封闭原则
装饰器定义:
在不改变原被装饰的函数的源代码以及调用方式下,为其添加额外的功能。
语法糖:@index = index = test_time(index)
用途:
登录认证,打印日志,访问记录
最简单的装饰器函数传参:
def warpper(s): #s = func1 def inner(*args,**kwargs): #4-聚合接受inner的实参大壮 print(666) #执行func1函数前操作 ret = s(*args,**kwargs) #5给打散实参执行func1函数, return ret #6将func1,return执行结果传给inner return inner #2-返回inner给warpper @warpper #1-语法糖:func1 = warpper(func1) def func1(a): return f"{a}欢迎登录" print(func1("大壮")) #3-func1 = inner('大壮') 7 print结果
装饰器传参:装饰器嵌套三层函数,最外层函数接受装饰器实参,可以对形参进行操作判断
def wrapper(n): def wrapper1(f): def inner(*args,**kwargs): name = input("输入用户名") pwd = input("输入密码") with open(n,encoding='utf-8') as f1: #n等于语法糖传过来的值 for i in f1: j,k = i.strip().split('|') if name == j and pwd == k: ret = f(*args,**kwargs) return ret return inner return wrapper1 @wrapper('qq') #将qq传给最外层wrapper形参 def qq(): print('成功访问qq') @wrapper('weixin') def tiktok(): print('成功访问微信') qq() tiktok()
两个装饰器,装饰一个函数:先执行@wraper1 执行完的结果是@wraper2的形参。
如果两个装饰器内层函数一样,调用函数时先走最上面装饰器
def wrapper1(func1): # func1 = f原函数 def inner1(): print('wrapper1 ,before func') # 2 func1() print('wrapper1 ,after func') # 4 return inner1 def wrapper2(func2): # func2 == inner1 def inner2(): print('wrapper2 ,before func') # 1 func2() # inner1 print('wrapper2 ,after func') # 5 return inner2 @wrapper2 # f = wrapper2(f) 里面的f == inner1 外面的f == inner2 @wrapper1 # f = wrapper1(f) 里面的f == func1 外面的 f == inner1 def f(): print('in f') # 3 f() # inner2()
递归函数
递归的最大深度1000层 : 为了节省内存空间,不要让用户无限使用内存空间
1.递归要尽量控制次数,如果需要很多层递归才能解决问题,不适合用递归解决
报错提示: RecursionError
修改递归的最大深度
import sys sys.setrecursionlimit(1000000000)
# 你的递归函数 必须要停下来 # 递归函数是怎么停下来的?递归3次结束整个函数 count = 0 def func(): # func1 global count count += 1 # 1 print(count) if count == 3: return func() print(456) func() def func(): # func2 global count count += 1 # 2 print(count) if count == 3: return func() print(456) def func(): # func3 global count count += 1 # 3 print(count) if count == 3:return func() print(456) 结果 1 2 3 456 456 后面的两个函数没有执行没有func() # 一个递归函数要想结束,必须在函数内写一个return,并且return的条件必须是一个可达到的条件 # 并不是函数中有return,return的结果就一定能够在调用函数的外层接收到
偏函数
就是当函数的参数太多的时候,需要简化,使用(functools.partial)可以创建一个新的函数,这个函数可以固定住原来的参数的部分参数,从而在调用的时候更加的简单。
functolls.partial的作用就是把一个函数的某些参数设置为默认值,然后返回一个新的函数,然后再调用这个函数。