12-Python高阶函数和装饰器

1.高阶函数

1.1 函数就是变量(**)

python中定义函数其实就是定义一个类型是 function 的变量,函数名就是变量名

def func1():
    print('函数1')

1)查看变量的类型

a = 10
print(type(a))    # <class 'int'>
print(type(func1))    # <class 'function'>

2)用一个变量给另外一个变量赋值

b = a
print(b+20)
c = func1
c() # func1函数的别名c

3)查看变量的地址

print(id(a))
print(id(func1))

4)修改变量的值

a = 100
\# func1 = 'abc' 不行

5)变量可以作为序列的元素

a = 200
list1 = [a, 10, 20, 30, func1, func1()]
print(list1)
print(list1[-2]())

1.2 高阶函数

1)实参高阶函数:参数是函数的函数就是实参高阶函数

a.变量可以作为函数的参数(实参)

def func2(x):
    print(x)

m = 12.5
func2(10)
func2(m)

b.实参高阶函数

def func3(x):
    print(x(1, 2))

func3(lambda m, n: m+n)

c.系统提供的常见的实参高阶函数

(a).
max、min、sorted都是实参高阶函数,有一个参数key需要传一个函数;被传入的函数需要一个参数和一个返回值,这个参数指向的是序列中的元素,返回值是比较对象

示例一:求列表中数字各位数和最大的元素

list2 = [19, 90, 78, 67]
def func3(item):
    # 个位数最大的元素
    # return item%10
    # 各位数的和的最大值
    sum1 = 0
    for x in str(item):
        sum1 += int(x)
    return sum1

print(max(list2, key=func3))

示例二:求列表中个位数最大的元素
print(max(list2, key=lambda item: item % 10))

练习1:用max函数获取学生列表中成绩最高的学生

students = [
    {'name': '张三', 'age': 18, 'score': 89},
    {'name': '小明', 'age': 29, 'score': 60},
    {'name': '李四', 'age': 25, 'score': 90},
    {'name': 'Tom', 'age': 19, 'score': 87}
]

获取成绩最高的学生

best_stu = max(students, key=lambda item: item['score'])
print(best_stu)

获取年龄最小的学生

min_stu = min(students, key=lambda item: item['age'])
print(min_stu)

将学生列表按照年龄值从小到大排序

new_students = sorted(students, key=lambda item: item['age'])
print(new_students)

(b).
map函数
map(函数,序列) - 将序列中所有的元素按照函数指定的规则进行转换, 返回的是map的对象(map就是容器型数据类型中的一种)。
函数需要一个参数和一个返回值,参数指向的是序列中的元素,返回值就是用来替换原来元素的新元素

list3 = [10, 20, 30, 40]

示例1:将列表list3中所有的元素都加1 -> [11, 21, 31, 41]

new_list3 = map(lambda item: item+1, list3)
print(new_list3, list(new_list3))

示例2:将列表list3中的所有的元素都转换成对应的字符串: [‘10’, ‘20’, ‘30’, ‘40’]

new_list3 = map(str, list3)
print(list(new_list3))    # ['10', '20', '30', '40']

©.
reduce(函数,序列) - 对序列中的元素按照函数提供的功能进行累积的操作
函数需要两个参数,第一个参数是初始化或者上次运算的结果,y指向每一个元素
reduce(函数,序列, 初始值)

from functools import reduce

list3 = [10, 20, 30, 40]

示例一: 求所有元素的和

result = reduce(lambda x, y: x+y, list3)
print(result)

示例二:求所有元素的乘积

result = reduce(lambda x, y: x*y, list3)
print(result)

示例三:求整个班级所有学生的总成绩

students = [
    {'name': '张三', 'age': 18, 'score': 89},
    {'name': '小明', 'age': 29, 'score': 60},
    {'name': '李四', 'age': 25, 'score': 90},
    {'name': 'Tom', 'age': 19, 'score': 87}
]
result = reduce(lambda x, y: x + y['score'], students, 0)
print(result)

reduce原码(粗糙的!)

def yt_reduce(func, seq, inter=None):
    if inter is None:
        x = seq.pop(0)
    else:
        x = inter

    for item in seq:
        x = func(x, item)

    return x

result = yt_reduce(lambda x, y: x+y['score'], students, 0)
print(result)

2)返回值高阶函数:返回值是函数的函数

func1是返回值高阶函数

def func1():
    def func2(x, y):
        return x + y
    return func2

print(func1()(100, 200))    # func2(100, 200)

2.装饰器

2.1 装饰器的作用

在不修改函数的情况下给函数添加新的功能

2.2 什么是装饰器

装饰器的本质就是一个函数(这个函数是一个实参高阶函数也是返回值高阶函数)

关键在于理解@装饰函数()…\n…def test() 时发生了什么

2.2.1 无参数装饰器

无参装饰器的写法:

def 函数名1(函数名2): # 2.函数名2=函数名4(4传过来赋值给2)

​ def 函数名3(*agrs, **kwargs):

​ 新功能代码

​ 返回值 = 函数名2(*agrs, kwargs) # 5.此处真正执行了函数4

​ return 返回值 # 6.这里返回值返回给函数名4调用处

​ return 函数名3 # 3.这里返回函数名3给函数名4调用处

@函数名1 # 1.这里会直接运行函数名1,把下面紧挨着的函数名传过去,没有为什么,语法规定
def 函数名4():
​ pass

函数名4() # 4.此时不再是调用函数名4,而是函数名3,相当于函数名3()

说明:
函数名1 - 装饰器名字,命名的时候和这个装饰器要添加的功能进行关联
函数名2 - 随便命名,指向被添加功能的函数; 可以命名成 fn
函数名3 - 随便命名,在原函数上添加完新的功能以后产生的新的函数
函数名4 - 原始函数,被装饰的函数
新功能代码 - 实现新加的功能的代码

写一个装饰器,在函数开始执行前打印’函数开始’

def start_function(fn):
    def new_fn(*args, **kwargs):
        print('函数开始')
        result = fn(*args, **kwargs)
        return result
    return new_fn

@start_function
def yt_sum(num1, num2):
    print(num1 + num2)

@start_function
def print_star(n):
    print('*'*n)

@start_function
def factorial(n):
    sum1 = 1
    for x in range(1, n+1):
        sum1 *= x
    return sum1

yt_sum(10, 20)

print_star(8)

yt_sum(100, 200)

print(factorial(5))

执行结果:
“”"
函数开始
30
函数开始

********
函数开始
300
函数开始
120

练习1:写一个装饰器将返回值是字符串的函数,返回值中所有的小写字母变成大写字母。

def yt_upper(fn):
    # fn = str_func
    def new_fn(*args, **kwargs):
        result = fn(*args, **kwargs)
        if type(result) == str:
            return result.upper()
        else:
            return result
    return new_fn

@yt_upper
def str_func():
    return 'abc'

装饰器的本质:

@yt_upper
def str_func():
    return 'abc'

相当于:

def str_func():
    return 'abc'
str_func = yt_upper(str_func)     #   str_func = new_fn


\# @yt_upper
\# def num_func():
\#     return 100


print(str_func())


\# print(num_func())

print('===================================')

补充:定义函数的时候*args和**kwargs同时存在的意义 - 不定参数的函数在调用的时候既可以使用位置参数也可以使用关键字参数

def func1(*args, **kwargs):
    pass

func1(20, 90, 90)
func1(a=20, b=9)
func1(1, 3, 4, a=3, b=5, c=90)

def func3(x, y, z):
    print(f'x:{x}, y:{y}, z:{z}')

def func2(*args, **kwargs):
    # args = (10, 20, 30)
    # kwargs = {}
    func3(*args, **kwargs)    # func3(10, 20, 30)   func3(*(10, 20, 30))  -> func3(10, 20, 30)

func2(10, 20, 30)
func3(x=20, y=90, z=300)

练习2:写一个装饰器,功能是计算指定功能的运行时间.

import time
def get_time(fn):  # fn=table  装饰器函数在@get_time后自动执行
    def inner(*args,**kwargs):
        import time
        start = time.time()
        result=fn(*args,**kwargs)
        end = time.time()
        print('函数运行时间为:{}'.format(end - start))
        return result
    return inner

@get_time
def table():  # 打印九九乘法表的功能
    for r in range(1, 10):
        for c in range(1, r + 1):
            print('{}*{}={}'.format(c, r, r * c), end="\t")
        print()
    time.sleep(1)
    return '我table函数执行完了'

print(table())  

程序执行结果如下:

“”"

11=1
1
2=2 22=4
1
3=3 23=6 33=9
14=4 24=8 34=12 44=16
15=5 25=10 35=15 45=20 55=25
1
6=6 26=12 36=18 46=24 56=30 66=36
1
7=7 27=14 37=21 47=28 57=35 67=42 77=49
18=8 28=16 38=24 48=32 58=40 68=48 78=56 88=64
19=9 29=18 39=27 49=36 59=45 69=54 79=63 89=72 9*9=81
函数运行时间为:0.00001
我table函数执行完了

“”"

无参数装饰器执行过程:
  1. @get_time的含义,是table=get_time(table)的一种简写。装饰器函数在被装饰函数定义好后立即执行,即@get_time \n def table()…后执行table=get_time(table),此时发生赋值fn=table,并执行get_time函数里的代码,返回了inner给被装饰的函数名table,发生赋值table=inner。
    总结:在def table()前加上@get_time相当于发生了fn=table,table=inner。此后table()调用就是执行了inner(),fn()调用就是执行了table(),inner 、table这两个函数分别换了两个别名 table 、fn。
  2. 此时再调用函数table(),不再是直接执行原table函数,因为table这个名字现在指向的已经是inner函数的地址,也就是执行了inner()。
  3. 执行inner函数,在inner函数里面,其中有一段代码就是新加的功能,此时inner函数里执行fn()时,因为前面fn=table的关系,fn这个名字已经指向了原table函数的地址,也就是执行了table(),注意这里才真正执行了被装饰的函数table,fn()的返回值给了result变量名。然后打印了程序执行时间,这是inner函数里新加的功能,此时再返回result也就是返回给了inner的调用者(调用处),也就是最后一行的table(),被装饰后最后一行的table函数就不是它本身,它就是inner()函数。
  4. 装饰器实际就是把被装饰的函数table()完全交给了内部函数inner()来管理。

2.2.2带参数装饰器

有时候,需要装饰器有参数,比如设计一个计时程序,用来测试网络训练Traing和测试Testing的时间,此时需要指定当前执行的是训练还是测试过程,为此,需要传入一个状态参数。此处说明一下装饰器传参的方式:@outer(args1,args2)等价于fn=outer(args1,args2)(fn),值得说明的是此处所谓的参数是指装饰器的参数而不是被装饰函数的参数,被装饰函数的参数是通过*args和**kwargs自然地传递的。

import time
def timing(status):
    print('timing执行了')
    def outer(fn):
        print('timing里面的outer执行了')
        def inner(*args, **kwargs):
            start = time.time()
            fn1 = fn(*args, **kwargs)
            print('[%s] time: %.3f s ' % (status, time.time() - start))
            return fn1 # 这是被装饰函数的返回值
        return inner
    return outer

@timing('Train') # 训练状态
def Training():
    time.sleep(2)
    return '我Train执行完了'
print(Training())

执行结果如下:

“”"

timing执行了
timing里面的outer执行了
[Train] time: 2.000 s
我Train执行完了

“”"

带参数装饰器执行过程:

1.@timing(‘Train’) \n def Training():…在此处等价于Training=timing(‘Train’)(Training)。

2.详解:timing(‘Train’)执行,这时返回outer给调用处,然后outer(Training)其实又跟无参数装饰器一样了。所以这里一下就调用了timing和outer两个函数。所以装饰器函数timing、outer函数依次立即执行。'Train’赋值给了status,Training赋值给fn,status=‘Train’,fn=Training。

3.执行timing函数,timing(‘Train’),打印’timing执行了’,然后return了outer给timing(‘Train’),即Training=timing(‘Train’)(Training)=outer(Training)。

4.执行outer函数,outer(Training),Training赋值给fn,打印’timing里面的outer执行了’,然后此函数return了inner给调用处(在@处),即Training=inner。(到这一步都只是定义Training所产生的操作,即即使不调用该函数,上两句话也会被打印)。

5.此时在最后一行执行Training(),本质上就是调用inner函数,记下起始时间start,执行fn函数,本质上是原Training函数,延时2秒,打印所用时间,返回fn1给了inner调用处,在最后一行,所以打印了’我Train执行完了’,打印的是原Training函数的返回值。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值