首页IT科技特殊的函数表达式有哪些类型(函数的特殊使用方式)

特殊的函数表达式有哪些类型(函数的特殊使用方式)

时间2025-05-04 00:31:38分类IT科技浏览3445
导读:5.4 函数的特殊使用方式 5.4.1 匿名函数...

5.4 函数的特殊使用方式

5.4.1 匿名函数

所谓匿名函数            ,即不再使用def语句这样标准形式定义的函数          。Python中可以使用lambda关键字来创建匿名函数                  。用lambda创建的匿名函数的函数体比def定义的函数体要简单     。语法如下:

lambda [参数1[,参数2],....参数n]]:表达式

lam_sum = lambda arg1, arg2: arg1 + arg2 print(lam_sum(10, 20))

30

上述代码中               ,第一行定义了一个lambda函数      ,执行两个数的和运算         ,并且把该lambda函数命名为lam_sum       。然后通过lam_sum()函数实现求和的功能                  。

Lambda创建的匿名函数中只能封装有限的逻辑进去        。

lambda函数拥有自己的命名空间               ,且不能访问自有参数列表之外或全局命名空间里的参数    。

实际上         ,一般在使用匿名函数时是不会再为创建的匿名函数命名的                 。因为这样失去了匿名函数的简便性           。在有些场景是需要传入函数      ,需要的逻辑并不是很复杂  。但是又不想再创建一个               ,这个时候就可以直接使用匿名函数了                。如下: print(list(map(lambda x: x * x, [1, 2, 3, 4, 5])))

[1, 4, 9, 16, 25]

5.4.2 递归调用

在Python定义函数时           ,函数体中可以调用其他函数   ,甚至可以调用自己              。这种自己调用自己的方式叫做递归调用。下面是一个递归式函数定义:

def recursion(): return recursion()

显然                ,对于上面定义的函数             ,如果你运行它,你将发现运行一段时间后              ,这个程序崩溃了(引发异常)             。

从理论上说               ,这个程序将不断运行下去   ,但每次调用函数时            ,都将消耗一些内存                 。因此函数调用次数达到一定的程度(且之前的函数调用未返回)后               ,将耗尽所有的内存空间      ,导致程序终止并显示错误消息“超过最大递归深度(maximum recursion depth exceeded         ,默认最大为1000次)          ”  。

可以通过以下代码修改最大递归深度: import sys sys.setrecursionlimit(99999)

这个函数中的递归称为无穷递归(就像以 while True 打头且不包含 break 和 return 语句的循环被称为无限循环一样)               ,因为它从理论上说永远不会结束          。你想要的是能对你有所帮助的递归函数         ,这样的递归函数通常包含下面两部分                  。

基线条件:满足这种条件时函数将直接返回一个值     。

递归条件:包含一个或多个调用      ,这些调用旨在解决问题的一部分       。

这里的关键是               ,通过将问题分解为较小的部分           ,可避免递归没完没了   ,因为问题终将被分解成基线条件可以解决的最小问题                  。

那么如何让函数调用自身呢?这没有看起来那么难懂        。前面说过                ,每次调用函数时             ,都将为此创建一个新的命名空间    。这意味着函数调用自身时,是两个不同的函数[更准确地说              ,是不同版本(即命名空间不同)的同一个函数]在交流                 。你可将此视为两个属于相同物种的动物在彼此交流           。

递归示例1:通过递归的方式求一个数的阶乘 def factorial(p_int=0): if p_int == 0: # 基线条件 return 1 else: # 递归条件 return p_int * factorial(p_int - 1) print(factorial(10))

3628800

递归示例2:通过递归的方式求幂

def power(x, n): return 1 if n == 0 else x * power(x, n - 1) print(power(2, 10))

1024

递归示例3:通过递归的方式解决汉诺塔问题

def move(n, a=A, b=B, c=C): if n == 1: print(移动, a, -->, c) else: move(n - 1, a, c, b) move(1, a, b, c) move(n - 1, b, a, c) move(4)

移动 A --> B

移动 A --> C

移动 B --> C

移动 A --> B

移动 C --> A

移动 C --> B

移动 A --> B

移动 A --> C

移动 B --> C

移动 B --> A

移动 C --> A

移动 B --> C

移动 A --> B

移动 A --> C

移动 B --> C

在某些特殊的问题中               ,如果通过普通的循环方式虽然也可以实现   ,但在使用了递归的方式后代码更加简单  。逻辑也更加清楚                。

理论上            ,所有的递归函数都可以写成循环的方式               ,但循环的逻辑不如递归清晰              。

使用递归函数需要注意防止栈溢出。在计算机中      ,函数调用是通过栈(stack)这种数据结构实现的         ,每当进入一个函数调用               ,栈就会加一层栈帧         ,每当函数返回      ,栈就会减一层栈帧             。由于栈的大小不是无限的               ,所以           ,递归调用的次数过多会导致栈溢出                 。

5.4.3 偏函数

参考:偏函数

介绍函数参数的时候   ,我们讲到                ,通过设定参数的默认值             ,可以降低函数调用的难度  。而偏函数也可以做到这一点          。

int()函数可以把字符串转换为整数,当仅传入字符串时              ,int()函数默认按十进制转换:

>>> int(12345)

12345

但int()函数还提供额外的base参数               ,默认值为10                  。如果传入base参数   ,就可以做N进制的转换:

>>> int(12345, base=8)

5349

>>> int(12345, 16)

74565

假设要转换大量的二进制字符串            ,每次都传入int(x, base=2)非常麻烦               ,于是      ,我们想到         ,可以定义一个int2()的函数               ,默认把base=2传进去:

def int2(x, base=2): return int(x, base)

这样         ,我们转换二进制就非常方便了:

>>> int2(1000000)

64

>>> int2(1010101)

85

functools.partial就是帮助我们创建一个偏函数的      ,不需要我们自己定义int2()               ,可以直接使用下面的代码创建一个新的函数int2:

>>> import functools

>>> int2 = functools.partial(int, base=2)

>>> int2(1000000)

64

>>> int2(1010101)

85

所以           ,简单总结functools.partial的作用就是   ,把一个函数的某些参数给固定住(也就是设置默认值)                ,返回一个新的函数             ,调用这个新函数会更简单     。

注意到上面的新的int2函数,仅仅是把base参数重新设定默认值为2              ,但也可以在函数调用时传入其他值:

>>> int2(1000000, base=10)

1000000

最后               ,创建偏函数时   ,实际上可以接收函数对象            、*args和**kw这3个参数            ,当传入:

>>> int2 = functools.partial(int, base=2)

实际上固定了int()函数的关键字参数base               ,也就是:

>>> int2(10010)

相当于:

>>> kw = { base: 2 }

>>> int(10010, **kw)

当传入:

>>> max2 = functools.partial(max, 10)

实际上会把10作为args的一部分自动加到左边      ,也就是:

>>> max2(5, 6, 7)

相当于:

>>> args = (10, 5, 6, 7)

>>> max(args)

10

当函数的参数个数太多         ,需要简化时               ,使用functools.partial可以创建一个新的函数         ,这个新函数可以固定住原函数的部分参数      ,从而在调用时更简单       。

5.4.4 闭包

闭包就是一种函数的嵌套               ,首先定义了一个函数           ,称之为外部函数                  。在这个外部函数体中又定义了一个内部函数   ,并且这个内部函数体中使用到了外部函数的变量        。外部函数最后return了内部函数    。那么这个外部函数以及内部函数就构成了一个特殊的对象                ,称之为闭包                 。

闭包避免了使用全局变量             ,使得局部变量在函数外被访问成为可能,相比较面向对象              ,不用继承那么多的额外方法               ,闭包占用了更少的空间           。

闭包示例 a = 1 def out_fun(b): c = 3 def in_fun(d): print(a + b + c + d) return in_fun infun = out_fun(2) infun(4)

10

可以看到   ,在内部函数in_fun中访问到了全局变量a               、外部函数out_fun的局部变量c以及参数b  。

5.4.4.2 装饰器

装饰器(decorator)的本质:函数闭包(function closure)的语法糖(Syntactic sugar)                。通过给函数装饰            ,可以加强函数的功能或者增加原本函数没有的功能              。

装饰器在第一次调用被装饰函数时进行增强               ,并且只增强一次。

让我们先从一个简单的函数开始吧             。假设现在有一个函数      ,用来计算从1到100累加之和         ,并输出结果                 。为了避免计算太快               ,我们在使用循环累加时设置等待0.01秒         ,函数定义如下: def mysum1(): from time import sleep total = 0 for _ in range(101): sleep(0.01) total += _ print(total) mysum1()

5050

此时      ,如果我们想要知道调用这个函数执行一共花了多少时间               ,我们可以在执行前后获取时间再经过计算得到           ,也可以通过改造函数   ,在函数内部函数体前后获取时间计算得到  。但是这两种方法都比较麻烦                ,尤其是第二种方法             ,需要侵入式修改函数代码          。

这个时候就可以通过为函数添加装饰器来实现了                  。下面是创建装饰器的一般方法:

装饰器的简单定义 def decorator1(func): def inner(): print(在这里执行被装饰函数执行前的增强操作) func() # 执行被装饰的函数 print(在这里执行被装饰函数执行前的增强操作) return inner

从上面可以看出,装饰器也是一个函数     。只不过装饰器接收的参数是被装饰的函数       。然后再装饰器内部定义一个函数              ,该内部函数体中执行要增强的操作代码以及执行被装饰的函数                  。最后再return该内部函数        。

接下来是用装饰器来对某个函数进行装饰    。我们以上面定义的mysum1函数来进行装饰:

装饰器的使用 @decorator1 def mysum1(): from time import sleep total = 0 for _ in range(101): sleep(0.01) total += _ print(total) mysum1()

在这里执行被装饰函数执行前的增强操作

5050

在这里执行被装饰函数执行前的增强操作

由上面可以看出               ,如果要装饰某个函数   ,只需要在定义这个函数时            ,在def语句的上一行添加@装饰器函数即可                 。

对于装饰器装饰一个函数: @decorator def myfun(): print("hello")

上面的代码等价于:

def myfun(): print("hello") myfun = decorator(myfun)

当一个装饰器装饰函数时               ,函数的功能增强了      ,因为在调用这个函数时         ,实际上调用的是在定义装饰器函数时               ,其内部函数           。而此时内部函数是由增强功能命令和原被装饰函数组成  。

创建一个统计函数运行时长的装饰器 import time def decorator1(func): def inner(): begin = time.time() func() # 执行被装饰的函数 end = time.time() print(f"函数`{func.__name__}`运行的总时间为:{end - begin:.3} 秒") return inner @decorator1 def mysum1(): from time import sleep total = 0 for _ in range(101): sleep(0.01) total += _ print(total) mysum1()

5050

函数mysum1运行的总时间为:1.59 秒 5.4.4.2.2 被装饰函数接收参数

在上面的例子中         ,通过装饰器函数decorator装饰的函数是不能有输入参数的      ,在实际使用中并不是很方便                。

通过对装饰器进行改造可以避免这种情况               ,从而使装饰器函数有更广泛的用途              。

装饰器定义:让被装饰函数接收参数 import time def decorator2(func): def inner(*args, **kwargs): begin = time.time() func(*args, **kwargs) # 执行被装饰的函数 end = time.time() print(f"函数`{func.__name__}`运行的总时间为:{end - begin:.3}") return inner

需要改造的地方:

1      、为装饰器函数decorator的内部函数inner在定义时增加收集位置形参和收集关键字形参

2         、在装饰器函数decorator的内部函数inner函数体中           ,执行被装饰器装饰的函数func时   ,通过参数解包的方式传入参数。

装饰带有参数的函数: @decorator2 def mysum2(a, b): from time import sleep total = a for _ in range(total + 1, b + 1): sleep(0.01) total += _ print(total) mysum2(1, 100)

5050

函数mysum1运行的总时间为:1.56 5.4.4.2.3 装饰器函数接收参数

通过上面对装饰器进行改造                ,可以使的被装饰的函数可以输入参数             。上面的装饰器函数decorator2可以计算被装饰的函数执行时间             ,但是只能获取到执行一次的时间                 。如果想要通过参数获取执行任一次的时间,则需要使得装饰器可以接收参数  。

装饰器定义:装饰器接收参数 import time def decorator3(n): def inner(func): def wrapper(*args, **kwargs): begin = time.time() for _ in range(iteration): func(*args, **kwargs) end = time.time() print(f"函数`{func.__name__}`运行的总时间为:{end - begin:.3}") return wrapper return inner

需要改造的地方:

1               、装饰器函数此时并不是通过参数来传入被装饰的函数              ,而是定义装饰器自己的参数               ,演示时使用的是一个位置参数n   ,在后续如果遇到比较复杂的情况下也可以使用关键字形参         、*args      、**kwargs等收集参数          。

2               、内部函数inner用来收集被装饰的函数                  。

3           、内部函数inner的内部函数wrapper用来收集被装饰的函数的参数     。并编写需要增强的命令       。最终要执行被装饰的函数其实就是执行这个wrapper函数                  。

装饰器接收参数: import time def decorator3(n): def inner(func): def wrapper(*args, **kwargs): begin = time.time() for _ in range(n): func(*args, **kwargs) end = time.time() print(f"函数`{func.__name__}`运行的总时间为:{end - begin:.3}") return wrapper return inner @decorator3(10) def mysum3(a, b): from time import sleep total = a for _ in range(total + 1, b + 1): sleep(0.01) total += _ print(total) mysum3(1, 10) # 等价于:mysum3 = decorator3(10)(mysum3)

55

55

55

55

55

55

55

55

55

55

函数mysum3运行的总时间为:1.41 5.4.4.2.4 装饰器的返回值

如果你了解了上一节的内容            ,很容易想到只要在wrapper函数中return就是被装饰函数的返回值        。

装饰器定义:接收被装饰函数的返回值 import time def decorator3(n): def inner(func): def wrapper(*args, **kwargs): begin = time.time() for _ in range(n): func(*args, **kwargs) end = time.time() print(f"函数`{func.__name__}`运行的总时间为:{end - begin:.3}") return end - begin return wrapper return inner

1   、在上面的代码中               ,return end – begin就是被装饰器的返回值    。可以通过变量进行接收                 。

@decorator3(3) def mysum3(a, b): from time import sleep total = a for _ in range(total + 1, b + 1): sleep(0.01) total += _ print(total) total_time = mysum3(1, 10) print(total_time)

函数mysum3运行的总时间为:1.42

1.4218323230743408 5.4.4.2.5 多个装饰器装饰同一个函数

对于某个函数      ,可以使用多个装饰器对其进行装饰         ,写法如下:

@decorator1 @decorator2 def 被装饰函数(): pass

对于被多个装饰器装饰的函数               ,其装饰顺序为由最近到远         ,即decorator2会先装饰      ,然后是decorator1           。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
关键词法跟谐音联想有什么区别(用“关键词联想生成器”优化你的文章) springmvc视图解析器原理(学习笔记——springMVC中视图及视图解析器对象;视图控制器)