函数的概述
- 什么是函数?
- 函数就是封装了一段准备好代码,在需要的时候可以被重复使用;
- 注意的地方:
def tell_story(): # 无参print('从前有座山')print('山上有座庙')print('......')age = int(input('请输入小孩子的年龄:'))if 0 < age < 5:if 0 < age < 3:print('孩子太小了,听不懂故事!')else:tell_story() # 这里直接调用函数elif age <= 0:print('请输入正确的年龄')else:print('孩子长大了')
def test1():print('123')test1() # 没有定义任何参数,直接调用test1()函数,执行print(),有print,则屏幕显示‘123’print('------')print(test1()) # 使用外部print()打印test1()函数,首先是执行函数体的print(),由于test1()函数中没有指定返回值,所以外部print()将None打印到屏幕
有参函数 (函数中带上参数变量)
常见的报错情况有:
- 设定函数的, 参数没有指定默认值时,调用函数没有填入该参数,则报错!
- 调用函数时, 没有按照定义函数的参数位置和顺序一 一对应填入,则报错!
- 位置参数需要放在关键字参数的后面,报错!
- 输入实参与形参数据类型不一致,报错!
位置参数 (不带默认值)
1,位置参数:调用函数时根据函数定义的参数位置来传递参数;
2,定义函数时,指定的参数叫做“形参”,主要用来占位的;函数只有被调用,才会被执行,调用时传入的参数称之为:实参;
3,调用函数时, 传递的实参和函数定义中的形参位置及顺序必须一致,顺序出错就会抛出异常提示:SyntaxError: non-default argument follows default argument; ```python def buy_water(money,name): print(‘{}每瓶售价{}元’.format(name,money))
buy_water(10,’康师傅矿泉水’) # 函数调用时,实参的位置和顺序必须一 一对应传递;
```pythondef get_birthday(id): # 定义函数名称并设定参数birthday = id[6:14] # 函数运算过程print(birthday) # 函数运算过程get_birthday('110123199001012121') # 调用函数get_birthday('120122199508081321') # 调用函数
关键字参数 (带默认值)
1,关键字参数:通过“建 = 值”的形式定义参数;
2,定义形参,传递实参,对位置和顺序没有要求,都可以任意顺序写入;
3,如果形参指定了默认值,调用时传入,则使用传入的值,如果不传入,则使用默认值;
4,如果形参没有指定默认值,则按位置参数传递实参,否则程序会抛出异常;
5,调用函数时,位置参数必须放在关键字参数的前面;
# 定义函数时,希望某些参数可以省略,例如: 通过身份证号码,获取出生日期或者年龄,则可以通过设定一个参数来控制函数如何返回结果, 比如下方代码中的get_age,使用True和False进行判断后输出.def get_birthday(id,age = False): # 设定参数, age为关键字参数, 默认值为Falseif age == True: # 对关键字参数进行判断return 2020-int(id[6:10]) # 返回计算公式的计算结果else:year = id[6:10] # 函数运算过程month = id[10:12] # 函数运算过程day = id[12:14] # 函数运算过程return year, month, day # 返回多个变量值# 传入,则使用传入的值result = get_birthday('42058119870601101X',True) # 调用函数,输入2个参数,输入True,满足条件,则执行if语句下的代码print(result)# 不传入,则使用默认值result = get_birthday('42058119870601101X') # 调用函数,省略第2个参数,注意:如果第2个参数原本没有赋默认值,省略的话,则会报错!print(result)
# 调用函数时,传入参数,则使用传入的值,不传入参数,则使用默认值def sleep(name,time=8):print('{}睡了{}小时'.format(name,time))sleep('小明',12)
def sleep(name,time=8,age=20 ):print('{}今年{}岁,今天中午睡了{}小时'.format(name,age,time))sleep('小明',12) # 函数调用时,传入参数,则使用传入的值,不传入参数,则使用默认值sleep('小明',age=12) # 如果想把12赋值给age, 则需要结合关键字的key来使用
def tt(a,b=2,c=3):print('a=',a,'b=',b,'c=',c)tt('x','y')tt(1)tt('x',c='y',b='z')
传入错误类型的参数
定义函数,往往对输入的参数有类型的要求,当输入了错误的类型,也会有异常发生;
# 例如,我们定义一个减法运算的函数,参数为两个整数def subtraction1(num1,num2):result=num1-num2return resultprint(subtraction1(3,2))# 上方代码中,调用函数时输入了两个整数,运行正常,接下来,如果输入错误的参数,比如两个字符:print(subtraction('3','2'))# 程序抛出异常,TypeError:unsupported operand type(s) for -: ‘str’ and ‘str’,提示内容为类型错误,不支持的操作数类型:'str’和'str’# 所以我们可以自定义这个异常的提示,我们知道,整数与小数可以支持减法,那么在函数中就可以通过判断传入的参数类型是否是这两种类型,如果不是,就给出异常提示:def subtraction2(num1,num2):if not isinstance(num1,(int,float)) or not isinstance(num2,(int,float)): # 判断参数是否为指定类型raise TypeError('参数类型错误,参数必须为整数或者小数') # 设定自定义异常内容result = num1 - num2return result# print(subtraction2('3',2)) # 调用函数,程序抛出异常# 上方代码中,使用内置函数isinstance( )对输入的参数值进行类型的比较,当任何一个参数不是int(整数)或者float(小数)类型时,都将抛出异常# 关键字raise用于引发异常,TypeError( )为异常的类型,括号中可以输入自定义的异常提示;
位置参数包装传递【元组型不定长参数】
不定长参数也叫可变参数:用于在不确定调用的时候会传递多少个参数的情况下(不传参也可以),这些参数被包装进一个元组中,被包装的参数可以是位置参数,也可以是关键字参数;
# 包装位置参数传递def user(*args): # 传进的所有参数都会被args变量收集,它会根据传进参数的位置合并成一个元组,args是元组类型,这就是包裹位置传递print(args)user() # ()user('zhangsan') # ('zhangsan',)user('zhangsan',18) # ('zhangsan', 18)user('zhangsan',18,'man') # ('zhangsan', 18, 'man')print('=============================\n')def arithmetic_mean(*args): # 加了星号 * 的args参数就会以元组(tuple)的形式导入sum = 0for x in args:sum += xreturn sum # 输入结果是一个元组,然后求元组中所有数值相加之和print(arithmetic_mean())print(arithmetic_mean(45))print(arithmetic_mean(45,32))print(arithmetic_mean(45,32,89,78))print(arithmetic_mean(8989.8,78787.78,3453,78778.73))print('=============================\n')def print_number(a,b,*args):print(a,b)print(args)print(type(args))c = a + bfor i in args:c += ireturn cx = print_number(1,2,10)print(x)print('=============================\n')def func(a,b,*,c): # 声明函数时,参数中星号 * 可以单独出现, 如果单独出现星号, * 后的参数必须用关键字传入return a + b + cy = func(1,2,c=3)print(y) # * 后的参数必须用关键字传入
关键字包装参数传递【字典型不定长参数】
传入的是多个关键字参数,在调用函数时, 使用**dic这种方式,实际上是将字典的键值对(key:values)拆包成了 (key=values) 关键字参数形式进行传递
def user(**kwargs): # 无论是包裹位置参数传递还是包裹关键字参数传递,都是一个组包的过程print(kwargs)print('==========包装关键字参数传递============')user(name = 'zhangsan')user(name = 'zhangsan',age = 18)user(name = 'zhangsan',age = 18,gender = 'man')dic1 = {'A':111,'B':222,'C':333}def test1(a,b,**kwargs): # **kwargs表示关键字参数(就是在调用的时候必须传入带赋值的实参),随便传入多少个都可以,实际上kwargs以字典的形式导入 ---装包的过程print(a)print(b)print(kwargs)print(type(kwargs))print('\n==========按照关键字参数传递============')test1(100,200,name='张三',age='22') # 调用时,必须传递关键字参数,按照key=value的形式传递print('\n==========将字典作为实参传入============')test1(100,200,**dic1) # 将字典作为实参传入时,使用**dic这种方式 (实际上就是将字典的键值对(key:values)拆包成(key=values) ----拆包的过程
参数传递顺序
如果一个函数中,有必备参数,默认参数,不定长参数,关键字不定长参数,需要注意这几个参数的先后顺序;
def total(name,age=10,*args,**kwargs):print(name,age,args,kwargs)total('小红') # 小红 10 () {}total('小明', 22, 1,2,3,4,5,phone=110,sex='男') # 小明 22 (1, 2, 3, 4, 5) {'phone': 110, 'sex': '男'}
拆包与装包
print('==========拆包:元组============')def test1():return 100,200x,y = test1()print(x)print(y)print('\n==========拆包:字典============')dic1 = {'name':'tom','age':18}a,b = dic1 # 对字典进行拆包,取出来的是字典的key,后续可以通过可以找到对应的valuesprint(a,dic1[a])print(b,dic1[b])
def test1(a,b=20,*args,**kwargs):print(a,b,args,kwargs)list1 = [3,4,5,6]dic1 = {'A':100,'B':200,'C':300}print('\n【如果列表没有被拆包传递】,则整个列表会作为元组中的一个元素输出:')test1(10,30,list1,x=88,y=99) # 10 30 ([3, 4, 5, 6],) {'x': 88, 'y': 99}print('\n【如果列表拆包后传递】,则列表就会转换成元组后输出:')test1(10,30,x=88,y=99,*list1) # 10 30 (3, 4, 5, 6) {'x': 88, 'y': 99}print('\n【如果字典没有拆包传递】, 则整个字典会作为元组中的一个元素输出:')test1(10,30,dic1,x=88,y=99) # 10 30 ({'A': 100, 'B': 200, 'C': 300},) {'x': 88, 'y': 99}print('\n【如果字典拆包后传递】,则会将字典的键值对(key:values)拆包成(key=values) 的形式后传递给函数:')test1(10,30,x=88,y=99,**dic1) # 10 30 () {'x': 88, 'y': 99, 'A': 100, 'B': 200, 'C': 300}
# 传入的是一个列表, --> 对列表拆包 --> 在函数中装包成一个元组list1 = ['c++','java','mysql','html']def func1(name,*args): # 传递进来的列表元素装包成了一个元组, *args = ('c++','java','mysql','html')if len(args) > 0:for i in args:print('{}学过了{}'.format(name,i))else:print('没有学过任何编程')func1('小明',*list1) # 将列表拆包后传递给函数
可变与不可变数据类型
- 不可变:变量引用的数据类型,在更改数值的时候,不存在不开辟新内存 的行为,此数据类型为不可变数据类型。【字符串,整型,浮点型,元组,不可变集合】
- 可变:变量引用的数据类型,在更改数值的时候,存在不开辟新内存 的行为,此数据类型为可变数据类型。【列表,字典,可变集合】 ```python print(‘不可变数据类型测试结果如下:’) s1 = ‘abc’ s2 = s1 # 此时,s1和s2指向的同一块内存地址
print(s2) # abc print(id(s1)) # 140001141830448 print(id(s2)) # 140001141830448
s1 = ‘def’
print(id(s1)) # 140001140882032【不可变数据类型 重点】
print(id(s2)) # 140001141830448
print(s2) # 打印出来还是‘abc’,字符串是不可变数据类型,如果要改变,则需要重新分配内存空间进行修改存储,所以,s1会发生了变化,而s2保持不变
print(‘\n可变数据类型测试结果如下:’) list1 = [1,3,4,5,7,9] list2 = list1 # 此时,list2和list1指向的同一块内存地址
print(list2) # [1, 3, 4, 5, 7, 9] print(id(list1)) # 140000683211904 print(id(list2)) # 140000683211904
list1.remove(5) # 当list1中发生了变化,list2也会随之改变 print(id(list1)) # 140000683211904 print(id(list2)) # 140000683211904 print(list2) # 打印出来没有5
```python# 交换两个变量的值a = 13b = 20# 方法一:使用第三个变量实现# c = b# b = a# a = c# 方法二:使用运算符来实现,只能是数字# a = a + b# b = a - b# a = a - b# 方法三:python独有的方法a,b = b,aprint(a,b)
return 语句
return 的特点
def test1(): # 特点1:函数结束时没有使用 return 语句,调用函数时返回Nones = 1 + 2def test2(): # 特点2:return 语句不带任何参数,调用函数时返回Nones = 1 + 2returnx = test2() # 特点3:函数有返回值,那么调用函数时,需要使用一个变量来接收它def test3():return 1 # 特点3:结束当前函数,该语句之后的语句将不再被执行,return 1 执行完后,退出当前函数,return 2不会被执行return 2
def test4(a):print('uuuuuuu')if a == 10:return # 条件满足,执行到return语句时,函数终止,后边的语句不再执行,注意,这里的return后面没有任何代码!print('kkkkkk')test4(10)
# 执行到return语句时,会退出函数,return之后的语句不再执行,但将return放在try语句块中,就会执行;def test1():print('98')return 'ok' # 执行到该return语句时,函数终止,后边的语句不再执行print('100')def test2():try:print('98')return 'ok' # 函数得到了一个返回值finally: # finally语句块中的语句依然会执行print('100')print(test1())print('----------')print(test2())
# 1,定义函数,求(n,m)之间的所有整数之和def add(n,m):x = 0for i in range(n,m):x += ireturn xresult = add(1,5) # 1+2+3+4=10print(result)
# 2, 定义函数,求一个数n的阶乘 比如:n=5 ===> 1x2x3x4x5=120def fac(n):x = 1 # 这里的容器变量初始化不能等于0,如果为0,0乘任何数都为0了for i in range(1,n+1):x = x * ireturn xresult = fac(5)print(result)
# 3, 计算m阶乘的和 比如:m=5 ===> 1!+2!+3!+4!+5!def fac_sum(m):x = 0for i in range(1, m + 1):x += fac(i)return xresult1 = fac_sum(5)print(result1)
return的返回值作为参数传递
def test1():return 100def test2(a):return a + test1()x = test1() # 保存函数test1的值y = test2(x) # 将函数返回值所在的变量作为参数传递给test2使用print(y)
return返回一个值
def test1(a,b):s = a + breturn sx = test1(100,200) # 既然使用return,就应该使用一个变量来接收这个返回值print(x) # 这个返回值可以使用print()打印出来
return返回多个值
当函数返回多个结果时:可以以列表,元组,字典的形式返回;
# 通过if条件判断后,可以使用多个return语句来返回满足条件的值def test1(a:int,b:int,c=0,d=False):s1 = a - bs2 = b - as3 = a + b + cif d:return {'A':s1,'B':s2,'C':s3} # 以字典的形式返回多个值elif a > b: # 默认以元组的形式返回多个值return a,b,s1elif a < b:return [a,b,s2] # 以列表的形式返回多个值else:return a,b,cresult1 = test1(2,1) # 关键字没有传入,则是用默认值,如果使用一个变量接收,则多个结果以元组的形式返回print('当a>b(返回元组型数据:)--->',result1)result2 = test1(1,2)print('当a<b(返回列表型数据:)--->',result2)result3 = test1(2,5,d = True)print('当d=True(返回字典型数据:)--->',result3)x,y,z = test1(1,1,1) # 如果使用多个变量接收,则一对一接收每个返回的结果print('当a=b(多变量接收返回)--->',x,y,z)
def get_birthday(id):year = id[6:10]month = id[10:12]day = id[12:14]return year, month, daybirthday = get_birthday('110123199001012121')print(birthday)y,m,d = get_birthday('110123199001012121')print('您的出生日期是%s年%s月%s日. '%(y,m,d))
return返回一个函数【闭包】
def test1(*args):def sum():x = 0for i in args:x = x + ireturn xreturn sum # 返回一个函数名test1(1, 2, 3, 4, 5) # 调用test1(), 此时该函数并没有执行,而是返回一个指向求和的函数名sum的内存地址,此时,test1 = test1.sumy = test1(1, 2, 3, 4, 5, 6, 7, 8, 9)print(type(y))y() # 调用y()函数,实际上就是调用sum()进行求和,这就是闭包.
def count():fs = []for i in range(1, 4):def f():return i * ifs.append(f)return fs # 返回一个列表f1, f2, f3 = count()print(f1())print(f2())print(f3())
执行过程:
- 当i=1, 执行for循环, 结果返回函数f的函数地址,存在列表fs中的第一个位置上;
- 当i=2, 由于fs列表中第一个元素所指的函数中的i是count函数的局部变量,i也指向了2; 然后执行for循环, 结果返回函数f的函数地址,存在列表fs中的第二个位置上;
当i=3, 同理,在fs列表第一个和第二个元素所指的函数中的i变量指向了3;然后执行for循环,结果返回函数f的函数地址,存在列表fs中的第三个位置上,所以在调用f1()的时候,函数中的i是指向3的
f1():return 3*3
同理f2( ), f3( )结果都为9;
- 闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量,即包在里面的函数(本例为f()),不要引用外部函数(本例为count())的任何循环变量.,如果一定要引入循环变量,方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
```python
def count():
fs=[ ]
for i in range(1,4):
return fsdef f(j):def g():return j*jreturn gfs.append(f(i))
f1,f2,f3=count() print(f1()) print(f2()) print(f3())
<a name="fb6128e2"></a>## 全局变量和局部变量---- **关键字: global nonlocal**- 定义在函数内部的变量拥有一个局部作用域,定义在函数外的拥有全局作用域- **变量作用域(LEGB):局部 -> 外层 -> 全局 -> 内置**- L:local 本地,局部变量,内部函数变量- E:encloseing 外层函数变量- G:global 全局变量- B:built-in 内置变量- 通过以下实例,你可以清楚了解Python函数变量的作用域:```python# a,b,c为全局变量,在整个py文件中都可以访问a = 100b = '你好'c = [1,2,3,4] # 列表属于可变类型def test():x = 123 # 在函数内部定义的变量叫作局部变量,只能在函数内使用print('内部变量x=',x)y = ['A','B','C']print('内部变量y=',y)a = 111 # 如果局部变量和全局变量同名,则函数内部的变量为全新定义的一个变量,这里不会修改全局变量的值print('内部变量a=',a)global b # 在函数内修改全局不可变类型的数据,使用global关键字,修改之后,全局变量也会随之改变b = '再见'c.append(88) # 在函数内可以修改全局中的可变数据类型数据(如:列表),不需要使用global关键字print('修改c之后的结果为:',c)def test1():nonlocal x # 在内部函数中可以修改外部函数中不可变类型的数据,使用nonlocal关键字,如果没有就继续往上直到找到为止x += 4print('修改内部函数中x之后的结果为:',x)y.append('DD') # 在内部函数中可以修改外部函数中可变类型的数据,不需要使用关键字print('修改y之后的结果为:',y)test1() # 注意,有函数嵌套的情况下,内层函数必须调用一下才行# 调用函数print('全局变量a=',a)print('全局变量b=',b) # 这里可以看见,在函数内对全局变量的修改已生效。’你好‘===》’再见‘test()print('调用函数之后全局变量b=',b)# print('locals = {},globasl = {}'.format(locals(),globals())) # 内置函数 globals() 和 locals() 可以查看全局变量和局部变量,以字典的形式输出
函数嵌套
- 函数的嵌套调用是在函数调用中再调用其它函数.
- 函数嵌套允许在一个函数中调用另外一个函数,比如有三个函数: ```python NAME = ‘nichoulas’ # 1 -> 首先执行NAME = ‘nicholas’语句
def jack(): # 2 -> 加载def jack():函数到内存进行编译,但不执行
name =’jack’ # 3.1 -> 执行name = “jack”语句
print(‘a,—->’,name) # 3.2 -> 执行print(name)语句,这里由于没有global关键字,优先读取局部变量name = “jack”,所以这里输出jack
def pony(): # 3.3 -> 加载def pony():函数到内存进行编译,但不执行name = 'pony' # 3.3.1 -> 执行name = "pony"语句,这里是一个局部变量print('b,--->',name) # 3.3.2 -> 执行print语句,这里由于没有global、nonlocal关键字,优先读取局部变量name = "pony",所以这里输出ponydef charles(): # 3.3.3 -> 加载charles():函数到内存进行编译,但不执行name = 'charles' # 3.3.3.1 -> 执行name = 'charles'语句,这里是个局部变量print('c,--->',name) # 3.3.3.2 -> 执行print(name)语句,优先读取局部变量name = "charles",所以这里输出charlesprint('d,--->',name) # 3.3.4 -> 执行print语句,这里由于没有global、nonlocal关键字,优先读取同一层级的局部变量name="pony",所以这里输出ponycharles() # 3.3.5 -> 调用charles():函数,开始执行pony() # 3.4 -> 调用pony():函数,开始执行print('e,--->',name) # 3.5 -> 执行执行print(name)语句,优先使用同层级的局部变量name = "jack",所以这里输出jack
jack() # 3 -> 调用jack()函数,开始执行
执行顺序:1——2——3——3.1——3.2——3.3——3.4——3.3.1——3.3.2——3.3.3——3.3.4——3.3.5—3.3.3.1—3.3.3.2——3.5
```pythondef outer():def inner():print('inner')print('outer')inner() # 函数有可见范围,这就是作用域的概念。内部函数不能被外部直接使用outer()
# 函数嵌套综合实验# 用户登录# 输入用户名# 输入密码# 输入验证码import random# 定义生成验证码的函数def generate_checkcode(n):s = '0123456789qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM'code = '' # 用一个空字符串来接收产生的随机字符for i in range(n): # 自定义循环次数ran = random.randint(0,len(s)-1) # 每次在字符串s中生成一个随机字符,赋值给ran.code += s[ran] # 每次将这个字符累加return code# 定义登录函数def login():username = input('输入用户名:')password = input('输入密码:')code = generate_checkcode(4) # 调用前面的函数得到一个验证码print('产生的验证码是:',code)code1 = input('输入验证码:')# 验证if code.lower() == code1.lower(): # 全是小写状态下,如果产生的随机验证码与输入的验证码相同,则验证码输入正确if username == 'xellgat' and password == '123456': # 验证码输入正确的情况下, 且用户名和密码也输入正确print('用户登录成功')else:print('用户名和密码输入有误')else:print('验证码输入不正确')# 函数调用login()
函数递归
- 函数的递归调用是在函数调用中再调用该函数自身; 递归函数每次运行需要等待, 占用内存。
- 递归函数一般有一个入口, 比如:函数传递的参数.
- 递归函数一般也有一个出口, 用于终止递归的循环, 一般使用条件判断来控制;
def food(x): # x就是入口,就是传递的值if x==1: # 1就是出口,就是用于终止递归循环的值,这里当x == 1 的时候, 终止递归函数的循环return 1else:return x + food(x-1) # 1, 4+Foo(4-1) => 2, 4+3+Foo(3-1) => 3, 4+3+2+Foo(1) => 4, 4+3+2+1+Foo(0)food(4) # 4+3+2+1
# 分析:先计算n=10,不符合if语句,往下继续计算的5,同理可得到2和1,计算到1时,符合if语句。所以返回1,再将1反馈到2,...最终得到结果就是1.def calc(n): # n 的值为入口print('每次递归n的值为:',n)if int(n/2) == 0: # 满足条件后的出口return nreturn calc(int(n/2))x = calc(10)print('x的值为:',x)
# 死循环def tell_story():print('从前有座山')print('山上有座庙')print('......')tell_story()tell_story()
# 解决死循环 通过一个[条件]来停止递归count = 0def tell_story():global countcount += 1print('从前有座山')print('山上有座庙')print('......')if count < 5:tell_story() # 满足条件才递归tell_story()
# 求1-n的和# 思路: 1+2+3+4+5+6# = sum(5)+6 # 当sum(5),又会中sum(n-1) + n 这条语句, 所以一直会加下去# = sum(4)+5+6# = sum(3)+4+5+6# = sum(2)+3+4+5+6# = sum(1)+2+3+4+5+6# = sum(0)+2+3+4+5+6 ==> 中了if条件,n = 0 ,直接返回 0 , 就是1+2+3+4+5+6count = 1def sum(n):if n == 0:return 0return sum(n-1) + n #例如:计算到6的和, 就是sum(6-1)+6sum(6)
# 求n (正数)的阶乘 - n! = n * (n-1)! ===> [ 1! = 1 * 0! , 0的阶乘是1]def fac(n):if n == 0: # 找到终止递归函数的出口条件,并返回一个值return 1return n * fac(n - 1)factorial(5) # 5*fac(5-1) ===> 5*4*fac(4-1) ===> 5*4*3*2*1*1=120
闭包(返回的是一个函数)
满足闭包的条件:
- 外部函数中必须定义了内部函数
- 外部函数必须有返回值,注意返回值是:内部函数名
内部函数中必须引用了外部函数的变量或者参数,该变量称之为内部函数的自由变量
我对闭包的理解
当函数存在嵌套,并且子函数引用了父函数中的变量,可以访问这些变量的作用域就形成闭包,如果子函数没有访问父函数中的变量,就不存在闭包;
- 打个比方: 一个大盒子,内部有一个小盒子,小盒子里用到一些东西是来自这个大盒子,那么这些来自大盒子的东西,就是闭包;
闭包的架构(个人理解:类似面向对象的结构):
- 定义外层函数 —-》类似定义类
- 外层函数变量 —》类似类属性
- 定义内层函数 —-》类似定义实例方法
- 调用自由变量 —-》类似调用类属性
```python
def func(a,b): # 类似定义类
c = 10 # 类似类属性
def inner_func(): # 类似定义实例方法
return inner_funcs = a + b + c # 类似调用类属性print('相加之后的和为:',s)
x = func(50,60) # 此时,x = inner_func =====> 类似通过类创建实例对象 x() # 相加之后的和为:120 =====> 调用实例方法
<a name="fda5309d"></a>### 应用1:多次实参传递不受影响调用闭包函数时,将实参传递给闭包所返回的值,不会受到下一次使用其它实参传递给闭包的影响<br />闭包函数中,引用的自由变量只和具体的闭包函数有关联,且闭包函数创建的每个实例所引用的自由变量都互不干扰```pythondef func(a,b):c = 10def inner_func():s = a + b + c # 在python中我们称上面的这个c为闭包函数inner_func的一个自由变量print('和是:',s)return inner_func # (注意点) 这里返回的是内部函数名变量,务必记住不能带(),因为带了()就是被调用的意思# 实例1x = func(5,6) # 实例1,此时,# x = func(5,6) = inner_funcprint(x) # 只要创建了一个内部函数,它就占用了一块内存地址x() # 第一次使用x调用函数返回的值为:21# 实例2y = func(50,60) # 实例2,既然有返回值,那么函数被调用时就必须使用一个变量来接收它的返回值;y()# 再次打印实例1print(x)x() # 第二次使用x调用函数返回的值为:21,不变,说明期间没有受到使用y调用函数传值的影响
应用2:可以同级调用
def func():a = 100def inner_func1():b = 90s = a + bprint(s)def inner_func2():inner_func1() # 可以调用同级的函数print('--->',a) # 可以使用同级的变量return 'hello'return inner_func2x = func() # x = inner_func2()print(x())
应用3:接收多个值
面这个例子中我们至少可以对闭包中引用的自由变量有如下的认识:
- 闭包函数中,引用的自由变量只和具体的闭包函数有关联,且闭包函数产生的每个实例所引用的自由变量都互不干扰;
- 一个闭包实例对其自由变量的修改会被传递到下一次该闭包实例的调用;【上述代码第22行处】
```python
def outer_func():
a = [] # 接收多个值
def inner_func(name):
return inner_funca.append(len(a)+1)print('%s = %s'%(name,a))
实例1
x = outer_func()
print(x)
x(‘list1’) # list1 = [1]
x(‘list1’) # list1 = [1, 2]
x(‘list1’) # list1 = [1, 2, 3]
print(‘==========================’)
实例2
y = outer_func() y(‘list2’) # list2 = [1] x(‘list2’) # list2 = [1, 2, 3, 4] 注意点:一个闭包实例对其自由变量的修改会被传递到下一次该闭包实例的调用 y(‘list2’) # list2 = [1, 2]
<a name="d06f21c5"></a>### 应用4:使用nonlocal关键字闭包中,如果要修改引用的外层变量,需要使用nonlocal 变量,否则当做是闭包内新定义的变量```pythondef test1():num = 10def test2():nonlocal num # 如果在内部函数中要修改外部函数的变量,需要使用nonlocal关键字num = 666print(num)print(num) # num = 10test2() # num = 666print(num) # num = 666return test2result = test1()result()
应用5:闭包变量所对应的值
在闭包内,如果引用了一个后期会发生变化的变量时,一定要注意:函数,必须是在被调用时, 才会去确定变量所对应的值;
def test1():a = 1def test2():print(a)a = 2 # 此时a = 2return test2result = test1() # result = test1() = test2result() # 结果为2,因为只函数被调用时,才会去确定函数体内变量标识符所对应的值,而函数体后面已经将a重新赋值为2,所有最后函数调用后得到的值也为2
应用6:错误的闭包
def test():funcs = []for i in range(3): # i = 0, i = 1, i = 2def inner(): # 定义一个闭包print(i*i)funcs.append(inner)return funcsx = test() # 创建实例x[0]() # 4x[1]() # 4x[2]() # 4
程序的结果并不是我们想象的0,1,4;实际结果全部都是4,这段代码是典型的错误使用闭包的例子;
该例子中比较特殊的地方就是:返回的所有闭包函数均引用了父函数中定义的同一个自由变量;而test( )返回的并不是一个闭包函数,而是一个包含了三个闭包函数的一个列表;
这里有个问题是:为什么for循环中的变量变化会影响到所有的闭包函数呢【取的是for循环最后那个值2】?
可是,我们上面说过:同一闭包的不同实例中引用的自由变量互相之间是没有影响的,那么问题到底出在哪里呢?
- 其实问题的关键就在于在返回闭包列表funcs之前,for循环中的变量值i已经发生变化了,而且这个改变会影响到所有引用它的内部函数;
- 因为在test( )函数返回前,其内部函数并不是闭包函数,只是一个内部定义的函数而已,函数必须调用,才能确定内部函数中变量所对应的值;
- 当然这个内部函数引用的父函数中的变量也不是自由变量,而只是当前局部中的一个本地变量而已;
应用7:修正错误的闭包
def test():funcs = []for i in range(3): # i = 0, i = 1, i = 2def outer(num): # 定义一个闭包的外层函数,接收一个参数,该参数是for循环中的i值def inner():print(num*num)return inner # 这是一个普通的闭包返回funcs.append(outer(i)) # 在for循环体内,【调用】闭包函数,那么每次循环都可以获取到精准的i值,然后将返回的函数添加进列表中return funcsx = test() # 创建实例x[0]() # 0x[1]() # 1x[2]() # 4
原始函数
def str1():print("hello workd")str1()
侵入原始函数进行修改
import timedef str1():star = time.time() # 侵入修改print("hello workd") # 这个是原函数代码块end = time.time() # 侵入修改print('程序运行时间为:',(end-star)*1000)if __name__ == '__main__':str1()
优点:
- 要想拓展原始函数代码的功能,最直接的办法就是侵入代码里面修改,例如:在原本的代码上添加时间戳(辅助功能),用于记录这个函数执行的总时间;
- 好处:逻辑函数和功能函数融合在一起了;
缺点:
- 不方便修改,因为是在原始函数体内进行修改,一旦代码量过多,修改起来很容易引起bug;
- 如果有一天你的boss和你说:“这段代码是公司的核心代码,不能直接去修改改,那该怎么办呢?
于是可以尝试将侵入到原始函数中的代码单独写成一个函数,然后将原始函数作为参数传递给该函数;
单独写一个函数来增强原函数
将功能函数 (添加时间戳) 分离出来,单独写一个功能函数timer( ),然后将原始函数作为参数传递给功能函数timer(func); ```python import time
原始函数
def str1(): # 把该函数名当作成一个变量,str1是一个变量,指向的是一块内存地址,str1()则表示调用函数,执行函数内部的操作 print(‘hello world’) time.sleep(2)
单独写一个功能函数
def timer(func): # 传递一个参数,该参数是一个函数变量
star = time.time()
func() # 中间插入接下来需要计算哪个函数执行的时间
end = time.time()
print(‘程序运行时间为:’,(end-star)*1000)
if name == ‘main‘:
timer(str1) # 使用功能函数调用原始函数
**优点:**- 避免直接侵入原函数中进行修改导致bug的产生;**缺点:**- 需要通过功能函数来调用原始函数名,使用起来不方便;- 这里的原函数指的是str1( ),而最后却是使用timer( )函数进行调用来拓展函数func的功能;我们的目标:能不能再调用原始函数的同时自动完成时间的统计;<a name="zHGZx"></a>## 通过闭包来增强原函数没有使用闭包:```pythonimport time# 原始函数def str1():print('hello world')time.sleep(2)def timer(func): # 函数名作为参数传递star = time.time()func() # 函数名 + () = 调用end = time.time()print('程序运行时间为:',(end-star)*1000)if __name__ == '__main__':str1 = timer(str1) # 其实这里已经将结果显示出来了,但是我们希望最后还是通过原始函数str1()来显示结果,所以这里应该返回一个函数名才行
上面的调试中,我们希望timer(str1)能够返回的是一个函数名,然后重新赋值给原始函数名,这样最后就可以通过str1( )的方式进行调用了,所以需要使用闭包模式!
使用闭包:
import time# 原始函数def str1():print('hello world')time.sleep(2)# 闭包def timer(func): # 闭包:用于增强函数func,闭包本质上也是一个函数,闭包函数的传入参数和返回值都是函数def inner():star = time.time()func()end = time.time()print('程序运行时间为:',(end-star)*1000)return inner # 返回内层函数名,因为我们上面的调试中要求返回一个函数名,然后重新赋值给原始函数名if __name__ == '__main__':str1 = timer(str1) # 所以timer(str1)返回的是函数名,赋值给原始函数名,从而使用闭包对原函数进行了增强str1() # 通过原始函数名进行调用
使用闭包的缺点:
- 这里定义了一个timer( )函数,它的参数是一个函数,然后给这个函数嵌入了计时功能,然后你可以拍着胸脯对老板说,看吧,不用动你原来的代码,我照样拓展了它的函数功能;
- 然后老板对你说: “我们公司核心代码区域有一千万个func( )函数,按你的方案,想要拓展这一千万个函数功能,就是要执行一千万次timer()函数,这可不行呀,我心疼我的机器”;
- 好了,你终于受够你老板了,准备辞职了,然后你无意间听到了装饰器这个神器,突然发现能满足你的要求了。
装饰器用于解决上面每次调用原始函数时,都需要使用闭包来增强原函数才行,所以让python自动增强原函数,则使用装饰器;
使用装饰器
优点:
- 既不需要侵入,也不需要调用其他函数来重复执行;
- 这里的timer()函数就是最原始的装饰器,它的参数是一个函数,然后返回值是一个函数名;
- 作为参数的这个函数str1( )是在返回函数inner( )的内部执行的;
- 在函数str1( )前面加上@timer,str1( )函数就相当于被注入了计时功能,现在只要调用str1( ),那么它就已经变身拥有“更多新的功能”的函数了;
- 所以这里的装饰器就像一个注入符号:有了它,拓展原来函数的功能,既不需要侵入函数内更改代码,也不需要重复执行原函数。 ```python import time
def timer(func): # 装饰函数
def inner(): # 闭包函数(不带参数)
star = time.time()
func()
end = time.time()
print(‘程序运行时间为:’,(end-star)*1000)
return inner
@timer # 原始函数上使用装饰函数,它相当于str1 = timer(str1)
def str1():
print(‘hello world’)
time.sleep(2)
if name == ‘main‘:
str1()
print(“str1.name is”,str1.name) # str1的name是inner
<a name="jF82X"></a>## 带有参数的装饰器```pythonimport timedef timer(func): # 装饰函数def inner(a,b): # 闭包函数(带指定参数)star = time.time()func(a,b)end = time.time()print('程序运行时间为:',(end-star)*1000)return inner@timerdef func1(a,b):print("hello,here is a func1 for add :")time.sleep(1)print("result is %d" %(a+b))if __name__ == '__main__':f = func1f(3,4)#func()
然后你满足了Boss的要求后,Boss又说:“我让你拓展的函数好多都是有参数的呀,有的参数还是个数不确定的那种,你的装饰器搞的定不?”然后你嘿嘿一笑,深藏功与名!
带有不定参数的装饰器
import timedef deco(func):def wrapper(*args, **kwargs): # 闭包函数(带无指定参数)startTime = time.time()func(*args, **kwargs)endTime = time.time()msecs = (endTime - startTime)*1000print("time is %d ms" %msecs)return wrapper@decodef func(a,b):print("hello,here is a func for add :")time.sleep(1)print("result is %d" %(a+b))@decodef func2(a,b,c):print("hello,here is a func for add :")time.sleep(1)print("result is %d" %(a+b+c))if __name__ == '__main__':f = funcfunc2(3,4,5)f(3,4)
最后,老板说:“可以的,小祁,我这里一个函数需要加入很多功能,一个装饰器怕是搞不定,装饰器能支持多个嘛”,最后你就把这段代码丢给了他
带返回值的装饰器
def zhuangshiqi(func):def inner(*args,**kwargs):print('='*30)res = func(*args,*kwargs) # 被装饰的函数如果有返回值,那么在装饰器中对应的函数代码也应该保持一致,进行返回return resreturn inner@zhuangshiqidef test1(x,y,z):s = x + y + zreturn s # 带返回值@zhuangshiqidef test2(x):print(x) # 没有返回值,那么返回的就是noneresult = test1(1,2,10) # 有返回值,使用变量接收print(result)test2(100)
多个装饰器
import timedef deco01(func):def wrapper(*args, **kwargs):print("this is deco01")startTime = time.time()func(*args, **kwargs)endTime = time.time()msecs = (endTime - startTime)*1000print("time is %d ms" %msecs)print("deco01 end here")return wrapperdef deco02(func):def wrapper(*args, **kwargs):print("this is deco02")func(*args, **kwargs)print("deco02 end here")return wrapper@deco01@deco02def func(a,b):print("hello,here is a func for add :")time.sleep(1)print("result is %d" %(a+b))if __name__ == '__main__':f = funcf(3,4)#func()'''this is deco01this is deco02hello,here is a func for add :result is 7deco02 end heretime is 1003 msdeco01 end here'''
多个装饰器执行的顺序就是从最后一个装饰器开始,执行到第一个装饰器,再执行函数本身,盗用网上一个例子:
def dec1(func):print("1111")def one():print("2222")func()print("3333")return onedef dec2(func):print("aaaa")def two():print("bbbb")func()print("cccc")return two@dec1@dec2def test():print("test test")test()
装饰器的外函数和内函数之间的语句是没有装饰到目标函数上的,而是在装载装饰器时的附加操作;17~20行是装载装饰器的过程,相当于执行了 test=dect1(dect2(test)),此时先执行 dect2(test),结果是输出 aaaa、将func指向函数test、并返回函数two,然后执行dect1(two),结果是输出1111、将func指向函数two、并返回函数one,然后进行赋值;用函数替代了函数名test, 22行则是实际调用被装载的函数,这时实际上执行的是函数one,运行到func()时执行函数two,再运行到func()时执行未修饰的函数test
其他资料
# 定义装饰器函数def decorate(func): # 定义一个名为decorate(装饰器)的函数,带的参数也为一个函数a = 100print('装饰器外层函数打印输出')def inner_func():func() # 调用一下外层函数中的参数(就是自定义的函数,它是作为参数传递给了外层函数)b = 200print('装饰器:',a+b) # 使用一下外层函数中的变量(闭包的特点)print('装饰器内层函数加载完成')return inner_func # 在外层函数下返回内层函数名(闭包的特点)# 调用装饰器, 实际上就是将自定义的函数送给装饰器函数进行加工,然后再返回给自定义的函数使用;# 1,@decorate用在哪个函数上面,则这个函数就会被当成参数传递给装饰器函数# 2,一旦使用@decorate, 就会直接执行decorate函数@decorate # 如果不使用装饰器,那么下面的函数只能输出函数内部的计算结果,如果使用了装饰器,那么可以将装饰器内容附加到该函数中def tt_test1(): # 自定义函数1print('这是我的自定义函数1')def tt_test2(): # 自定义函数2print('这是我的自定义函数2')# 再不调用自定义函数时,就会直接执行装饰器函数,而装饰器内有返回的值, 它是被tt_test接收的: tt_test = inner_funcprint(tt_test) # decorate.<locals>.inner_func# 最后调用自定义函数,查看结果tt_test1()tt_test2() # 自定义函数2没有使用装饰器的情况下
import timedef decorate(func):def wrapper():print('正在校验中...')time.sleep(2)print('校验完毕')func() # 调用一下原函数(即装饰器的参数,即自定义的函数)return wrapper@decoratedef test1():print('---one---')@decoratedef test2():print('---two---')test1()test2()
# 带参数(定义一个万能的装饰器)import timedef decorate(func):def wrapper(*args,**kwargs): # 装包成一个元组print('\n正在校验中...')time.sleep(2)print('校验完毕')# 调用一下原函数(即装饰器的参数,即自定义的函数), 原函数是带参数的,所以这里也要将wrapper的参数传递给原函数.func(*args,**kwargs) # 拆包, 因为args是一个元组,这里带*args就是将整一个元组拆开,这样就可以将元素分别赋值到test2中的两个参数中return wrapper# 传递一个空参数@decoratedef test():print('第一次输出:test')test()# 传递一个参数@decoratedef test1(n): # test1 = wrapperprint('第二次输出:',n)test1(8)# 传递两个参数@decoratedef test2(name,age): # test2 = wrapperprint('第三次输出:',name,age)test2('张三',20)# 传递一个列表参数@decoratedef test3(students,clazz = '1905'): # test3 = wrapperprint('第四次输出:\n{}班级的学生如下:'.format(clazz))for i in students:print(i)students = ['lily','tom','lucy']test3(students,clazz='1900')
# 多层装饰器(以装修为例)def zhuangxiu_1(func): # 第一个装修公司print('----->1,start')def wrapper(*args,**kwargs):func()print('刷漆')print('----->1,end')return wrapperdef zhuangxiu_2(func): # 第二个装修公司print('----->2,start')def wrapper(*args,**kwargs):func()print('铺地板')print('----->2,end')return wrapper#同时使用两个装修公司装修@zhuangxiu_1@zhuangxiu_2 # 离自定义函数最近的装饰器,先干活,干完活之后再整体送给上一个装饰器def house():print('这是我的毛坯房')# 调用house()
# 带参数的装饰器(只要装饰器带参数,那么定义装饰器的时候就是三层函数)def outer(x): # 第一层:负责接收装饰器的参数def inner_func(func): # 第二层:负责接收自定义函数def wrapper(*args,**kwargs): # 第三层:负责接收自定义函数中的参数func(*args)print('当前正在铺第{}块地砖'.format(x))return wrapper # 返回的是第三层return inner_func # 返回的是第二层@outer(10)def house(time):print('{}年拿到房子的钥匙'.format(time))house('2019-10-1')@outer(x=100)def street():print('新的街道名为:东风路')street()
# 模拟付款流程import timeislogin = False # 指定登录状态,默认是没有登录的# 定义一个登录函数def login():username = input('请输入用户名:')password = input('请输入密码:')if username == 'admin' and password == '123456':return Trueelse:return False#定义一个装饰器,用于验证付款验证(就是用户在付款前做一下登录验证)def login_required(func):def wrapper(*args,**kwargs):global isloginprint('-----正在进行付款操作-----')# 验证用户是否登录if islogin: # 如何登录为True,表示登录成功,就执行自定义函数func(*args,**kwargs)else: # 如果没有登录成功,则跳转到登录页面,进行身份验证print('用户没有登录,不能付款')islogin = login() # 调用登录函数,由于有返回值,需要使用变量来接收(这个变量可以指定为全局的islogin)print('result:',islogin)return wrapper@login_required# 自定义付款过程的函数def pay(money):print('正在付款,付款金额是{}元'.format(money))print('付款中,请稍后...')time.sleep(2)print('付款完成!')#调用pay(100)pay(200)
匿名函数
- 使用 lambda 来创建匿名函数, 所谓匿名,就是不使用def语句的形式定义函数。
- lambda 只是一个表达式,函数体比 def 简单很多。
- lambda 的主体是一个表达式,而不是一个代码块, 仅仅能在lambda表达式中封装有限的逻辑进去。
- lambda 函数拥有自己的命名空间,它不能访问自己参数列表之外或全局命名空间里的参数。
- 格式:lambda 参数1, 参数2… : 返回的结果 ```python result = lambda a,b:a+b # sum相当于函数名, a,b相当于参数, a+b相当于return返回的结果 print(result) # 输出结果是一个内存地址,表明sum就是一个函数变量名 print(result(10,20)) # 调用sum函数
1,无参数传递
fn1 = lambda:100 print(fn1())
2.传递一个参数
fn2 = lambda a: a # 为了验证传递的参数是否成功,所有将直接将参数返回 print(fn2(‘hello world’))
3.默认参数传递
fn3 = lambda a,b,c=100: a+b+c print(fn3(10,20)) # 130 print(fn3(10,20,30)) # 60
4.传递可变参数(位置参数,返回的是一个元祖)
fn4 = lambda *args: args print(fn4(10)) #(10,) print(fn4(10,20)) #(10,20) print(fn4(10,20,30,40)) #(10,20,30,40)
4.传递可变参数(关键字参数,返回的是一个字典)
fn5 = lambda **kwargs: kwargs print(fn5(name=’python’)) #{‘name’:’python’} print(fn5(name=’python’,age=20)) #{‘name’:’python’,’age’:20}
```python# 匿名函数作为参数def test(x,y,func):print(x,y)print(func)s = func(x,y) # func函数的定义print(s)print(func(x,y))# 调用testtest(1,2,lambda a,b:a+b)
import oshost = 'gza-access8'oid = {'interName':'1.3.6.1.2.1.2.2.1.2'}get_oid = lambda snmpoid:os.popen('snmpwalk -c public -v 2c %s %s'%(host,snmpoid)).readlines()print(get_oid) # 结果显示get_oid就是一个函数get_oid(oid['interName'])get_cvoid = lambda snmpoid,values:os.popen('snmpwalk -c public -v 2c %s %s.%s'%(host,snmpoid,values)).readlines()print(get_cvoid) # 结果显示get_oid就是一个函数# get_cvoid(oid['interName'])get_joid = lambda logicname,snmpoid:os.popen('snmpwalk -c %s/default@public -v 2c %s %s'%(logicname,host,snmpoid)).readlines()print(get_joid) # 结果显示get_oid就是一个函数# get_joid(oid['interName'])get_jvoid = lambda logicname,snmpoid,values:os.popen('snmpwalk -c %s/default@public -v 2c %s %s.%s'%(logicname,host,snmpoid,values)).readlines()print(get_jvoid)# get_jvoid()
# 匿名函数与内置函数的结合使用 --- max()
# 取出列表list1中,字典元素中"a"是最大值的那个列表元素。
list1 = [{'a':10,'b':20},{'a':30,'b':20},{'a':15,'b':20}]
# max这个内在函数存在一个key参数,它用于接收一个函数,使用lambda,i为每次从list1中取出的一个字典元素,将每个字典中键为“a”的值返回给key,最后在key中比较
m = max(list1,key = lambda i:i['a'])
print(m) # {'a': 30, 'b': 20}
# 匿名函数与内置函数的结合使用 --- map()
# map(func,*iterables) 对列表中的元素一一进行操作的时候,使用map()函数
print('-----map()函数简单使用-----')
list1 = [1,2,3,4,5,6,7,8]
x = map(lambda i:i + 2,list1)
print(x)
print(list(x))
print('\n-----带if判断的lambda表达式-----')
func = lambda j:j if j % 2 == 0 else j + 10
print(func)
y = func(3)
print(y)
print('\n-----套上map()函数-----')
result = map(func,list1)
print(result)
print(list(result))
# 匿名函数与内置函数的结合使用
# reduce() 对可迭代的数组中元素进行加减乘除运算的函数 ---> reduce(function,sequence, initial=None)
from functools import reduce
tuple1 = (1,2,3,4,5,6,7,8,9)
x = reduce(lambda i,j:i+j,tuple1)
print(x) # 45
# 说明:
# 1,2:1+2 --> 返回3
# lambda 3,3:3+3 --> 返回6
# lambda 6,4:6+4 --> 返回10
# lambda 10,5:10+5 --> 返回15
# lambda 15,6:15+6 --> 返回21
# lambda 21,7:21+7 --> 返回28
# lambda 28,8:28+8 --> 返回36
# lambda 36,9:36+9 --> 返回45
print('\n-----如果可迭数组中只有一个元素的情况下:-----')
tuple2 = (3,)
print(reduce(lambda i,j:i+j,tuple2))
print(reduce(lambda i,j:i+j,tuple2,1000)) # 后面的1000表示的是initial = 1000,就是初始化值.
# 匿名函数与内置函数的结合使用 --- filter()
# 用于过滤
list1 = [11,8,7,15,20,5]
# 取出list1中大于10的元素
list(filter(lambda i:i > 10,list1))
# 匿名函数与内置函数的结合使用 --- sorted()
dict1 = [{'name':'A','age':20},
{'name':'C','age':19},
{'name':'B','age':18},
{'name':'E','age':21},
{'name':'D','age':23},
{'name':'F','age':22}]
# 找出字典中,age大于20的元素
print(list(filter(lambda i:i['age'] > 20,dict1)))
# 对age进行从大到小排序
list(sorted(dict1,key = lambda j:j['age']))
