1. 高阶函数
接受函数作为参数,或者将函数作为返回值返回的函数就是高阶函数。
1.1 案例:将指定列表中所有的偶数保存到新的列表
lst=[1,2,3,4,5,6,7,8,9,10]
def fn(l): # 定义一个函数,将指定列表中的偶数保存到新的列表
#参数l是要筛选的任意列表
new_lst = [] # 创建一个空列表用来保存符合要求的元素
for i in l:
if i%2==0: #判断寄偶
new_lst.append(i) #将符合要求的元素添加进新列表
return new_lst #返回新列表
print(fn(lst))
结果
1.2 案例:将指定列表中所有的奇数保存到新的列表
只需要将上述代码中判断条件改动一些即可:
lst=[1,2,3,4,5,6,7,8,9,10]
def fn(l): # 定义一个函数,将指定列表中的奇数保存到新的列表
#参数l是要筛选的任意列表
new_lst = [] # 创建一个空列表用来保存符合要求的元素
for i in l:
if i%2!=0: #判断寄偶,将条件改成恒不等于0
new_lst.append(i) #将符合要求的元素添加进新列表
return new_lst #返回新列表
print(fn(lst))
结果
1.3 案例:将指定列表中所有大于5的数保存到新的列表
将上述代码的判断条件再次改动一下:
lst=[1,2,3,4,5,6,7,8,9,10]
def fn(l): # 定义一个函数,将指定列表中大于5的数保存到新的列表
#参数l是要筛选的任意列表
new_lst = [] # 创建一个空列表用来保存符合要求的元素
for i in l:
if i>5: #判断是否大于5
new_lst.append(i) #将符合要求的元素添加进新列表
return new_lst #返回新列表
print(fn(lst))
结果
1.4 定义一个具有判断奇偶功能的函数
def fn1(i):
if i%2==0:
return True
将上述函数整体传递到下面的代码中:
def fn(l):
new_lst = [] # 创建一个空列表用来保存符合要求的元素
for i in l:
if fn1(i): 此处判断条件直接用刚才定义的函数代替,如果判断结果返回真,下面的程序执行
new_lst.append(i) #将符合要求的元素添加进新列表
return new_lst #返回新列表
print(fn(lst))
结果
如果我又想要一个奇数列表怎么办呢?上述代码再改动一下
lst=[1,2,3,4,5,6,7,8,9,10]
def fn1(i): #定义一个函数判断奇偶
if i%2==0:
return True
# 将判断奇偶的函数当作判断条件,传递到下面的代码中
def fn(l):
new_lst = [] # 创建一个空列表用来保存符合要求的元素
for i in l:
if not fn1(i): 此处判断条件也用刚才定义的函数代替,如果判断结果返回不是真,下面的程序执行
new_lst.append(i) #将符合要求的元素添加进新列表
return new_lst #返回新列表
print(fn(lst))
结果
1.5 定义一个判断是否大于5 的函数
如果我又想要一个大于5的数呢?
那么我同上面一样定义一个函数专门用来判断是否大于5
lst=[1,2,3,4,5,6,7,8,9,10]
def fn1(i):
if i%2==0:
return True
# 定义一个专门用来判断是否大于5的函数
def fn2(i):
if i>5:
return True
# 将判断奇偶的函数当作判断条件,传递到下面的代码中
def fn(l):
new_lst = [] # 创建一个空列表用来保存符合要求的元素
for i in l:
if fn2(i): #此处判断条件也用刚才定义的函数fn2代替,如果判断结果返回是真,下面的程序执行
new_lst.append(i) #将符合要求的元素添加进新列表
return new_lst #返回新列表
print(fn(lst))
结果
1.6 高阶函数概念引入
通过上面的案例我们可以看出,当判断条件后面的具有判断功能的函数变了
if (具有判断功能的函数):
那么整体的输出结果也就相应的改变。那么根据我们的要求,就可以将函数内的判断语句处传递一个相应的规则就可以了。
那么我们把上面的代码改动一如下:
lst=[1,2,3,4,5,6,7,8,9,10]
def fn1(i):
if i%2==0:
return True
# 定义一个专门用来判断是否大于5的函数
def fn2(i):
if i>5:
return True
# 将判断奇偶的函数当作判断条件,传递到下面的代码中
def fn(func,l): # 函数的形参改成两个,第一个是传递一个函数,表示这里的形参希望传入一个函数对象
new_lst = [] # 创建一个空列表用来保存符合要求的元素
for i in l:
if func(i): #此处判断条件用形参func替代
new_lst.append(i) #将符合要求的元素添加进新列表
return new_lst #返回新列表
print(fn(fn1,lst)) # 在调用函数的时候,要传入两个实参,第一个是我们想要的规则的函数,
# 千万不要加括号,加括号等于调用函数,我们这里只是传参;第二个是一个待筛选的列表。
结果
假如我们想实现筛选大于5的数的功能,只需要将fn2传入即可
如果我们想要扩展其他的功能,我们只需要在函数外面定义该函数,然后使用的时候传入就行了。比如我们定义一个筛选3的倍数的函数。
# 定义一个判断3的倍数的函数
def fn3(i):
if i%3==0:
return True
整体代码如下:
lst=[1,2,3,4,5,6,7,8,9,10]
def fn1(i):
if i%2==0:
return True
def fn2(i):
if i>5:
return True
# 定义一个判断3的倍数的函数
def fn3(i):
if i%3==0:
return True
def fn(func,l):
new_lst = []
for i in l:
if func(i):
new_lst.append(i)
return new_lst
print(fn(fn3,lst))
从以上案例我们可以看到,当我们传入的参数函数变了,相应的功能就改变了,这就是我们要讲的高阶函数。
高阶函数有什么好处???
我们以前传参的时候都是传的,变量,数值,字符串,列表,都是单一的数据对象。但现在我们传递的是一个函数,里面包括很多的代码。
当我们把一个函数当作参数传递的时候,实际上是将指定的代码传递到目标函数。
2. 匿名函数
对于一些语法比较简单的函数,可以使用匿名函数创建,占内存更少,有利于做产品优化。
2.1 filter函数
filter函数的两个形参是:
- function函数
- interable可迭代对象(序列)
我们通过一个实例来学习使用这个函数
lst=[1,2,3,4,5,6,7,8,9,10]
def fn4(i):
if i%3==0:
return True
return False
print(filter(fn4,lst))
可以看到输出结果是一个filter类型的对象,后面是对象的内存地址。
如果我们要取出满足要求的序列,我们可以用list()函数转化一下
lst=[1,2,3,4,5,6,7,8,9,10]
def fn4(i):
if i%3==0:
return True
return False
print(list(filter(fn4,lst)))
这里的结果就是列表了。这个函数也是把函数当作参数传递的,所以也是高阶函数。
2.2 匿名函数lambda表达式
lambda表达式可以用来创建一些简单的函数,它是函数的另一种创建方式。
语法:lambda 参数列表 : 返回值
例如:我们定义一个函数,实现两个数的和,一般是这样做的:
def fn(a,b):
return a+b
现在我们可以用lambda表达式这样做:
print(lambda a,b : a+b)
可以看到返回的是一个lambda函数对象 ,并没有实际结果。如果我们要求两个数的和,需要传入参数并调用。
print((lambda a,b : a+b)(100,200))
可以看到跟我们之前定义的函数输出结果是相同的。
也可以把lambda表达式这样使用:
fn6 = lambda a,b : a+b
print(fn6)
print(fn6(123,456))
lambda表达式的好处就是方便简单,用完之后就消失,不占内存。
适用于一些简单语法的函数,不适合复杂的语法规则。
3. 闭包
将函数作为返回值返回的也是高阶函数
例题:我们定义一个嵌套函数
def fn1():
def fn2():
print('我是fn2')
return fn2
print(fn1())
可以看到返回的是一个函数对象。
再调用一次就会将内容打印出来。
也可以这样:
def fn1():
def fn2():
print('我是fn2')
return fn2
r=fn1()
print(r)
r()
如果有多个函数,返回的是谁,调用的时候就会打印出谁。
def fn1():
def fn2():
print('我是fn2')
# return fn2
def fn3():
print('我是fn3')
return fn2
r=fn1()
r()
返回一下fn3
def fn1():
def fn2():
print('我是fn2')
# return fn2
def fn3():
print('我是fn3')
return fn3
r=fn1()
r()
3.1 回顾变量的作用域
def fn1():
def fn2():
a=10
print('我是fn2',a)
# return fn2
def fn3():
print('我是fn3')
return fn2
r=fn1()
r()
这样可以把a值打印出来
在函数的外面是不能打印出来的
def fn1():
def fn2():
a=10
print('我是fn2',a)
# return fn2
def fn3():
print('我是fn3')
return fn2
r=fn1()
# r()
print(a)
这就好像内部有一个包
- 这种函数,以函数为返回值的函数,我们也称之为闭包。
- 通过闭包我们可以创建一些只有当前函数能够访问的变量,我们可以将一些私有的或者重要的数据藏在闭包中。
例题:定义一个函数求列表内元素的平均值。
lst = [10,20,30,40,50]
def fn():
return sum(lst)/len(lst)
现在我们优化一下,可以让这个函数求你t添加后的数的平均值。
lst=[]
def fn(n):
lst.append(n)
return sum(lst)/len(lst)
print(fn(10))
print(fn(20))
我们看到,每传递一次,列表都在改变。
也就是说,我们的每一次操作都会影响lst的值。能不能让后面的操作不影响这个变量呢?当然可以,这就是我们说的闭包的作用。
例题:
def make_fn():
lst = []
def fn3(n):
lst.append(n)
return sum(lst)/len(lst)
return fn3
r=make_fn()
print(r(10))
lst = []
print(r(20))
闭包的形成条件:
- 函数嵌套
- 将内部函数作为返回值返回
- 内部函数必须要使用到外部函数的变量
4. 装饰器
创建几个简单功能的函数:
def add(a,b):
#求任意两数的和
print('开始计算... ...')
r = a+b
print('结束计算... ...')
return r
def mul(a,b):
#求任意两数的积
return a*b
print(add(1,2))
print(mul(3,4))
结果
我们可以直接修改代码去修改函数的功能,但如果要修改的函数比较多,修改起来比较麻烦。维护起来也不方便。也违反开闭原则(ocp:该原则要求对开发程序的扩展,要求关闭程序的修改。意思就是说,你可以扩展程序的功能,但是不要修改我程序的源码)。
例题:我希望在不修改原函数的前提下来对函数进行扩展
def fn():
print('我是函数fn... ...')
def fn2():
print('函数开始了... ...')
fn()
print('函数结束了... ...')
fn2()
这样我们看到函数fn的功能扩展了,但是没有修改函数fn的代码。我们也可以用这个办法对其他函数进行扩展。
例题:
def add(a,b):
r=a+b
return r
def new_add(a,b):
print('函数开始')
r=add(a,b)
print('函数结束')
return r
r=new_add(123,456)
print(r)
结果
这里每扩展一个函数的功能要定义一个新的函数,也不方便。我们能不能定义一个函数,可以扩展所有函数的功能而不改变他们的源码呢?当然可以,这就是我们要讲的装饰器。
4.1 装饰器的推理过程
例题:定义一个新的函数,用来对其他函数进行功能的扩展,扩展的功能是添加两个打印语句“函数开始执行”,“函数执行结束”。
第一步,创建一个函数:
def start_end():
#里面再嵌套一个新函数
def new_function():
pass
# 返回新的函数
return new_function
#设置一个变量接受这个函数
f=start_end()
#打印一下返回的应该是一个函数对象
print(f)
结果
下面我们看看这个装饰器的代码如何写,根据以上经验,我们可以把要扩展的函数添加进来。
def fn():
print('我是函数fn')
def start_end():
#里面再嵌套一个新函数
def new_function():
print('函数开始了....')
fn()
print('函数结束了....')
# 返回新的函数
return new_function
#设置一个变量接受这个函数
f=start_end()
#打印一下返回的应该是一个函数对象
#print(f)
f()
#我们先调用一下这个函数
结果
我们发现确实对函数fn进行扩展了,那对别的函数能扩展吗?比如对函数add()进行扩展。我们可以把函数的形参设置为old意思是旧的函数,里面涉及到的要扩展的函数都设置为old。
def fn():
print('我是函数fn')
def start_end(old):
#里面再嵌套一个新函数
def new_function():
print('函数开始了....')
old()
print('函数结束了....')
# 返回新的函数
return new_function
现在我们先调用这个装饰器对函数fn()进行装饰。
#设置一个变量接受这个函数
f=start_end(fn) #传入函数fn作为实参
f() #调用f
结果:
下面我们再对函数add()进行装饰试试。
def add(a,b):
r=a+b
return r
def start_end(old):
def new_function():
print('函数开始了....')
old()
print('函数结束了....')
return new_function
f=start_end(add)
f()
结果
报错了,报错显示add()缺少两个必须的位置参数。那我们改动一下代码,加入两个位置参数:
def add(a,b):
r=a+b
return r
def start_end(old):
def new_function(a,b):
print('函数开始了....')
result=old(a,b) #这里需要设置变量来接受结果用来返回
print('函数结束了....')
return result
return new_function
f=start_end(add)
r= f(1,2) #因为有返回值,这里也要设置返回值
print(r)
结果
确实已经扩展了函数add(),但是如果我们回头再用这个装饰器扩展fn()函数,又因为有参数而报错。那有没有一种方法可以接收很多参数,而且无论你的函数有没有参数都可以对其进行装饰呢?有的。是否还记得不定长参数*a和不定长关键字参数**a的知识呢?
请看如下代码:
def fn():
print('我是函数fn')
def add(a,b):
r=a+b
return r
def start_end(old):
def new_function(*a,**b):
print('函数开始了....')
result=old(*a,**b) #这样写就可以了
print('函数结束了....')
return result
return new_function
f=start_end(add)
r=f(1,2)
print(r)
结果
再来装饰一下函数fn()
def fn():
print('我是函数fn')
def add(a,b):
r=a+b
return r
def start_end(old):
def new_function(*a,**b):
print('函数开始了....')
result=old(*a,**b) #这样写就可以了
print('函数结束了....')
return result
return new_function
f=start_end(fn)
f()
结果
我们可以看到整个装饰器已经可以适应各种函数了。
类似start_end这一类的函数就叫装饰器,通过装饰器可以在不修改其代码的情况下对其他函数进行扩展。在开发中都是用装饰器进行功能扩展的。
4.2 装饰器的使用
但一般装饰器不是这样调用的。比如我有这么一个函数需要装饰:
def speak():
print('同学们,大家晚上好!')
可以直接在紧挨着函数的上方写上
@start_end
来使用装饰器,我们试一下
@start_end
def speak():
print('同学们,大家晚上好!')
speak()
结果
再试一下
@start_end
def fn():
print('我是函数fn')
fn()
再来最后一次
@start_end
def add(a,b):
r=a+b
return r
r=add(1,2)
print(r)
结果
好的,装饰器知识到此就讲完了。当然,这是一个简单功能的装饰器,你用的时候可以自行扩展。
5. 命名空间
5.1 用locals()来获取当前作用域的命名空间
如果locals()在全局里使用获取的就是全局的命名空间,如果在函数内使用获取的就是函数的命名空间,命名空间返回的是一个字典。
例如:
a=10
def fn():
print('你好')
scope=locals()
print(scope)
结果:
D:\Python38\python.exe D:/work/基础/Day11/Demo01.py
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000027AB53308B0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'D:/work/基础/Day11/Demo01.py', '__cached__': None, 'fn6': <function <lambda> at 0x0000027AB547E160>, 'a': 10, 'fn': <function fn at 0x0000027AB547E1F0>, 'scope': {...}}
Process finished with exit code 0
这里返回的是一些特殊方法,或者称为“魔术方法”。
我们可以看出scope返回的是一个字典,我们可以通过字典的知识来打印一个a的值
print(scope['a'])
print(a)
可以看到这两种方式的返回值一模一样。我们再玩儿一个酷的,例如,整个代码中全程都没有定义c变量,但是我们可以用字典的知识去添加,然后再打印。理论上应该是报错c这个变量not defined,但是请看。
scope['c']=20
print(scope['c'])
结果是
我们没有定义c,只是在命名空间里添加了一个键值对。就相当于在全局中创建了一个变量。但python不支持这种方法,所以不建议你这么做。
如果我们在函数的内部使用locals()就获取函数的命名空间。
def fn():
a=123
scope=locals()
print(scope)
fn()
那么在局部空间能不能这样玩儿呢?
def fn():
a=123
scope=locals()
scope['c']=30
print(scope)
这个要看python的版本,我的是3.8版本的,是可以的。
打印就不行了,不信你瞧。
报错了。所以你编程时千万不要这样玩儿。还有保存文件时一定要使用英文,最好不要出现中文。