高阶函数

高阶函数的定义:函数的参数或返回值也为函数时,这种函数称之为高阶函数。
高阶函数的特点(必须至少满足一个):**

  1. 接受一个或多个函数作为参数
  2. 将函数作为返回值返回
# 例题1:如下普通函数,是对列表list1进行筛选,找到是偶数的元素
list1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
def fun(list1):
    list2 = []
    for i in list1:
        if i % 2 == 0:
            list2.append(i)
    return list2
print(fun(list1))
# 运行结果 》》》[2, 4, 6, 8, 10]

# 更改为高阶函数
list1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
def fun1(i):
    if i % 2 == 0:
        return True

def fun(fun, list1):
    list2 = []
    for i in list1:
        if fun(i):
            list2.append(i)
    return list2

print(fun(fun1, list1))
#运行结果 》》》[2, 4, 6, 8, 10]

匿名函数

匿名函数,就跟他的字面意思一样,我们不对函数进行命名,因为我们在开发的时候是协同开发,如果每个人写一个函数,都对其进行一个命名的话,会非常占用命名空间,对同事也不是特别友好,因为不能取相同的函数名,不然函数将会被覆盖,所以必须将一些非常简单效果的函数写成匿名函数

匿名函数 lambda函数 就是专门用来创建一些简单的函数

lambda函数

下面是一些lambda函数示例:
lambda x, y: x*y 函数输入是x和y,输出是它们的积x*y
lambda:None 函数没有输入参数,输出是None
*lambda args: sum(args) 输入是任意个数的参数,输出是它们的和(隐性要求是输入参数必须能够进行加法运算)
**lambda kwargs: 1 输入是任意键值对参数,输出是1
实际操作(对任意2个数进行添加并输出的操作):

例题2print((lambda a, b: a + b)(10, 20))
运行结果 》》》30

例题3:
r = lambda a, b: a + b
print(r(10, 20))
运行结果 》》》30

filter类
filter() 需要传递两个参数 按照你设定的规则,过滤出你想要的数据
1、 传递一个函数
2 、传递一个需要过滤的序列(可迭代的)

filter类的使用:依旧是对数据筛选的更改

filter类 与 lambda函数结合使用写高阶函数

例题1的高阶函数,我们可将fun1()lambda函数代替,再加上filter类的使用,既能简化我们的代码,没有函数重名的现象。

list1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result = lambda i: i % 2 == 0
print(list(filter(result, list1)))
运行结果 》》》[2, 4, 6, 8, 10]

闭包

函数返回值也是函数的高阶函数也称为闭包,也就是说闭包必须有函数嵌套

闭包的定义需要满足以下三个条件:
1、在一个外函数中定义了一个内函数
2、内函数里运用了外函数的临时变量
3、并且外函数的返回值是内函数的引用

闭包的第一个特性:变量不被销毁

def func_out(num1):
    def func_inner(num2):     # 函数嵌套
        result = num1 + num2  # 外层参数调用
        print(result)

    return func_inner   # 将内部函数作为返回值返回

f = func_out(1)  # 给num1传一个1
f(2)             # 函数调用,给num2传一个2 #运行结果 》》》3
f(3)   # 运行结果 》》》4

这里我们就可以看到闭包的这一特性,在f(2)运行后,并不会将num1的值销毁,所以在f(3)执行的时候,依旧可以调用num1的值

闭包的第二个特性:变量不可被更改

def func_out(num1):
    def func_inner(num2):     # 函数嵌套
        num1 = 10
        result = num1 + num2  # 外层参数调用
        print(result)

    print(num1)         #这里就可以很清晰的看到num1的变化
    func_inner(2)
    print(num1)

    return func_inner   # 将内部函数作为返回值返回

func_out(1)   # 函数对象是func_out 函数调用是func_out()

运行结果 》》》
1
12
1

首先我们这样更改,在内层函数中加上了num1=10,我们等于没有调用外层函数的参数num1,这个外层函数的num1行参与内层函数的变量num1压根就不是一个东西,所以这已经不满足闭包的条件:内部函数必须要使用到外部函数的变量,所以这个函数连闭包都不是。

闭包中让外部变量可以修改
  我们先要了解一个相对的概念,如上述的num1,在整个py文件中,他是局部变量,但相对于内部函数,他就是全局变量,这里我们说的让外部变量修改就是对num1进行更改。
这里我们用到了nonlocal,也就是非本地的意思,将num1变量改成外部的参数,这样我们又形成了一个闭包,也让外部变量num1变得可有修改。

def func_out(num1):
    def func_inner(num2):     # 函数嵌套
        # 告诉解释器,此处使用的是外部变量num1
        nonlocal num1
        # 这里的本意是要修改外部变量num1的值,实际上是重新进行了赋值
        num1 = 10
        result = num1 + num2  # 外层参数调用
        print(result)

    print(num1)
    func_inner(2)
    print(num1)

    return func_inner   # 将内部函数作为返回值返回

func_out(1)   # 函数对象是func_out 函数调用是func_out()
运行结果 》》》
1
12
10

15、装饰器的引入
为什么要引入装饰器?
我们可以直接通过修改函数中的代码来完成需求,但是会产生以下一些问题

如果修改的函数多,修改起来会比较麻烦
不方便后期的维护
这样做会违反开闭原则(ocp)
程序的设计,要求开发对程序的扩展,要关闭对程序的修改
我们这里有一个简单的函数
def add(a, b):
return a + b
1
2
如果我们直接,修改,如下,就会违反开闭原则

不要这样使用
def fun1():
print(‘函数开始执行’)
print(‘我是fun1函数’)
print(‘函数执行完毕’)

正确使用装饰
装饰fun1()函数
def fun1():
print(‘我是fun1函数’)

def fun():
print(‘函数开始执行’)
fun1()
print(‘函数执行完毕’)

fun()
运行结果 》》》
函数开始执行
我是fun1函数
函数执行完毕

装饰add()函数

def add(a, b):
return a + b

def fun(a, b):
print(‘函数开始执行’)
result = add(a, b)
print(result)
print(‘函数执行完毕’)

fun(1, 2)
运行结果 》》》
函数开始执行
3
函数执行完毕

这样使用装饰器虽然也可以,但是我们发现每次装饰都要改很多东西,所以我们讲一下通用装饰器
def fun1():
print(‘我是fun1函数’)

def add(a, b):
return a + b

def fun(fn, *args, **kwargs):
print(‘函数开始执行’)
r = fn(*args, **kwargs)
print®
print(‘函数执行完毕’)

fun(add, 1, 2)
运行结果 》》》
函数开始执行
3
函数执行完毕

fun(fun1)
运行结果 》》》
函数开始执行
我是fun1函数
None
函数执行完毕

我们将fun函数定义几个行参,第一次是fn,可以传不同的函数,后面就是不定长参数。之后在调用的时候,实参写上我们想要传递的参数就可以辽!!!

16、装饰器的使用

前面的装饰运用只是简单介绍一下概念使用,但是他还不是正式的装饰器,因为正式的装饰器是一个闭包
通过装饰器,可以在不修改原来函数的情况下来对函数进行扩展
在开发中,我们都是通过装饰器来扩展函数的功能的

闭包条件:
函数嵌套
将内部函数作为返回值返回
内部函数必须要使用到外部函数的变量

拿上述对add()函数装饰进行修改

def add(a, b):
    return a + b

def fun_out(fn, *args, **kwargs):
    def fun_inner():
        print('函数开始执行')
        r = fn(*args, **kwargs)
        print(r)
        print('函数执行完毕')
    return fun_inner

f = fun_out(add, 1, 2)  
f()
运行结果 》》》
函数开始执行
3
函数执行完毕

写到这里肯定会产生很多问号,为啥子要闭包,还嵌套一个东西那么麻烦,结果效果还一样,就像我刚开始听完这个,其实上面那样用还没有满足我们的需求,因为我们每次需要装饰函数的时候,都得重新修改,很是麻烦,所以我们还要讲一下@的用法。

通用装饰器

def fun_out(fn, *args, **kwargs):

    def fun_inner(*args, **kwargs):
        print('函数开始执行')
        r = fn(*args, **kwargs)
        print(r)
        print('函数执行完毕')

    return fun_inner

需要被装饰的函数

@fun_out            # 等价于 f=fun_out(fun)
def add(a, b):
    return a + b

add(1, 2)
运行结果 》》》
函数开始执行
3
函数执行完毕

我们举个例子介绍一下为什么要装饰,当一个功能是公用的时候,我们将其写成装饰,就会很方便,比如我们在逛淘宝的时候,不管我们干什么,都需要进行登录,我们就可以把登录的程序写成装饰,然后我们在写其他功能的时候就可以直接@他,直接修改我们需要装饰的函数即可。就不用每写一次都修改各种参数。

课后作业

作业1. 请使用装饰器实现已存在的函数的执行所花费的时间。
time模块
简单介绍一下time模块
1、延迟功能
import time #一定要导入time模块,下面其他的就不导入了
time.sleep(秒数) # 想让程序停顿几秒钟 实现延迟功能

# 延迟功能举例
print(1)
time.sleep(2)
print(2)

# 结果,在打印了1后会停2秒,然后再打印2

2、获取时间戳
时间戳是指格林威治时间自1970年1月1日(00:00:00 GMT)至当前时间的总秒数。

import time 
print(time.time())
运行结果 》》》
1612519773.541305

3、时间戳转化为元组

now_time = time.localtime(time.time())
print(now_time)

运行结果 》》》
time.struct_time(tm_year=2021, tm_mon=2, tm_mday=5, tm_hour=18, tm_min=9, tm_sec=33, tm_wday=4, tm_yday=36, tm_isdst=0)

序号 属性 值
0 tm_ year 2021(当年的年份)
1 tm_ mon 1到12 (当前的月份)
2 tm_ mday 1到31 (当前的天)
3 tm
hour 0到23 (时)
4 tm_ min 0到59 (分)
5 tm
sec 0到61 (60或61是闰秒) (秒)
6 tm_ wday 0到6 (0是周一) (周)
7 tm_ yday 1到366(儒略历) (一年的第多少天)
8 tm_ isdst -1, 0, 1, -1是决定是否为夏令时的旗帜
4、格式化时间

res = time.strftime("%Y-%m-%d-%H-%M-%S")
print(res)
运行结果 》》》
2021-02-05-18-47-35

符号 含义
%a 本地(locale)简化星期名称
%A 本地完整星期名称
%b 本地简化月份名称
%B 本地完整月份名称
%c 本地相应的日期和时间表示
%d 一个月中的第几天(01 – 31)
%H 一天中的第几个小时(24 小时制,00 – 23)
%l 一天中的第几个小时(12 小时制,01 – 12)
%j 一年中的第几天(001 – 366)
%m 月份(01 – 12)
%M 分钟数(00 – 59)
%p 本地 am 或者 pm 的相应符
%S 秒(01 – 61)
%U 一年中的星期数(00 – 53 星期天是一个星期的开始)第一个星期天之前的所有天数都放在第 0 周
%w 一个星期中的第几天(0 – 6,0 是星期天)
%W 和 %U 基本相同,不同的是 %W 以星期一为一个星期的开始
%x 本地相应日期
%X 本地相应时间
%y 去掉世纪的年份(00 – 99)
%Y 完整的年份
%z 用 +HHMM 或 -HHMM 表示距离格林威治的时区偏移(H 代表十进制的小时数,M 代表十进制的分钟数)
%Z 时区的名字(如果不存在为空字符)
%% %号本身
接下来我们进入实例(时间戳的使用):

作业2. 求前20个斐波那契数列所需时间

# 用以前学过的方式编写
import time
a = time.time()

# 使用函数求前20个斐波那契数列 fun(n)=fun(n-1)+fun(n-2)
def fun(n):
    # 基线条件
    if n <= 1:
        return 1
    # 递归条件
    return fun(n-1)+fun(n-2)
for i in range(20):
    print(fun(i), end=' ')

print()     # 换行
b = time.time()
c = round((b-a),3) # 取小数点后3位并四舍五入
print(f'斐波那契数列函数运行一共花了{c}秒')

运行结果 》》》
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 
斐波那契数列函数运行一共花了0.005
# 使用装饰器和闭包实现
import time

def get_time(fun1):
    def inner():
        now = time.time()
        fun1()
        end = time.time()
        c = round((end - now), 3)
        print(f'斐波那契数列函数运行一共花了{c}秒')
    return inner

# 使用函数求前20个斐波那契数列 fun(n)=fun(n-1)+fun(n-2)
def fun(n):
    # 基线条件
    if n <= 1:
        return 1
    # 递归条件
    return fun(n-1)+fun(n-2)

@get_time
def fun1():
    list1 = []
    for i in range(20):
        list1.append(fun(i))
    print(list1)

fun1()

运行结果 》》》
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]
斐波那契数列函数运行一共花了0.005

本文地址:https://blog.csdn.net/zsdutm/article/details/113775196