前言:
零基础学Python:Python从0到100最新最全教程。 想做这件事情很久了,这次我更新了自己所写过的所有博客,汇集成了Python从0到100,共一百节课,帮助大家一个月时间里从零基础到学习Python基础语法、Python爬虫、Web开发、 计算机视觉、机器学习、神经网络以及人工智能相关知识,成为学习学习和学业的先行者!
欢迎大家订阅专栏:零基础学Python:Python从0到100最新最全教程!
一、 闭包
闭包定义:
Python函数是支持嵌套的。 如果在一个内部函数中对外部函数作用域(非全局作用域)的变量进行引用,那么内部函数就会被称为闭包。闭包需要满足如下3个条件:
存在于两个嵌套关系的函数中,并且闭包是内部函数;
内部函数引用了外部函数的变量(自由变量);
外部函数会把内部函数的函数名称返回。
闭包示例:
二、装饰器
1.装饰器的概念
假设我们已经开发了一个本有的函数,后续可能会增加临时的需求,例如插入日志,我们可以增加一个包裹函数,由它来负责这些额外的需求,这个包裹函数就是 装饰器
。
装饰器主要应用在如下场景:
- 引入日志;
- 函数执行时间统计;
- 执行函数前预备处理;
- 执行函数后清理功能;
- 权限校验;
- 缓存。
装饰器是一个函数,它需要接收一个参数,该参数表示被修饰的函数。例如,有如下一个装饰器函数:
def myDectoration(func):
def inner():
print("正在执行内部函数")
func()
return inner
def printMessage():
print("--------欢迎您-------")
pm = myDectoration(printMessage)
pm()
# 正在执行内部函数
# --------欢迎您-------
- 装饰器是个嵌套函数
- 内部函数是一个闭包。
- 外部函数接收的是被修饰的 函数(func)
通过在函数定义的前面添加@符号和装饰器名,实现装饰器对函数的包装
。给f1函数加上装饰器,示例如下:
@w1
def f1():
print(’f1')
此时,程序会自动编译生成调用装饰器函数的代码,等价于:
f1 = w1(f1)
2. 多个装饰器
多个装饰器应用在一个函数上,调用顺序是从下至上
。
@w1
@w2
def f1():
print(‘---f1---’)
执行顺序:
先执行@w2,后执行@w1
3.装饰有参数的函数
看看下面的代码,运行结果是什么呢?
def w1(func):
def inner(a,b):
print('开始验证权限')
func(a,b)
return inner
@w1
def tes(a,b):
print('a=%d,b=%d'%(a,b))
tes(1,2)
# 开始验证权限
# a=1,b=2
三、递归调用
Python中允许函数嵌套定义,也允许函数之间相互调用,而且一个函数还可以直接或间接的调用自身。
def fac(num):
if num in (0, 1):
return 1
return num * fac(num - 1)
上面的代码中,fac
函数中又调用了fac
函数,这就是所谓的递归调用。代码第2行的if
条件叫做递归的收敛条件,简单的说就是什么时候要结束函数的递归调用,在计算阶乘时,如果计算到0
或1
的阶乘,就停止递归调用,直接返回1
;代码第4行的num * fac(num - 1)
是递归公式,也就是阶乘的递归定义。下面,我们简单的分析下,如果用fac(5)
计算5
的阶乘,整个过程会是怎样的。
# 递归调用函数入栈
# 5 * fac(4)
# 5 * (4 * fac(3))
# 5 * (4 * (3 * fac(2)))
# 5 * (4 * (3 * (2 * fac(1))))
# 停止递归函数出栈
# 5 * (4 * (3 * (2 * 1)))
# 5 * (4 * (3 * 2))
# 5 * (4 * 6)
# 5 * 24
# 120
print(fac(5)) # 120
注意,函数调用会通过内存中称为“栈”(stack)的数据结构来保存当前代码的执行现场,函数调用结束后会通过这个栈结构恢复之前的执行现场。栈是一种先进后出的数据结构,这也就意味着最早入栈的函数最后才会返回,而最后入栈的函数会最先返回。例如调用一个名为a
的函数,函数a
的执行体中又调用了函数b
,函数b
的执行体中又调用了函数c
,那么最先入栈的函数是a
,最先出栈的函数是c
。每进入一个函数调用,栈就会增加一层栈帧(stack frame),栈帧就是我们刚才提到的保存当前代码执行现场的结构;每当函数调用结束后,栈就会减少一层栈帧。通常,内存中的栈空间很小,因此递归调用的次数如果太多,会导致栈溢出(stack overflow),所以递归调用一定要确保能够快速收敛。我们可以尝试执行fac(5000)
,看看是不是会提示RecursionError
错误,错误消息为:maximum recursion depth exceeded in comparison
(超出最大递归深度),其实就是发生了栈溢出。
我们使用的Python官方解释器,默认将函数调用的栈结构最大深度设置为1000
层。如果超出这个深度,就会发生上面说的RecursionError
。当然,我们可以使用sys
模块的setrecursionlimit
函数来改变递归调用的最大深度,例如:sys.setrecursionlimit(10000)
,这样就可以让上面的fac(5000)
顺利执行出结果,但是我们不建议这样做,因为让递归快速收敛才是我们应该做的事情,否则就应该考虑使用循环递推而不是递归。
再举一个之前讲过的生成斐波那契数列的例子,因为斐波那契数列前两个数都是1
,从第3个数开始,每个数是前两个数相加的和,可以记为f(n) = f(n - 1) + f(n - 2)
,很显然这又是一个递归的定义,所以我们可以用下面的递归调用函数来计算第n
个斐波那契数。
def fib(n):
if n in (1, 2):
return 1
return fib(n - 1) + fib(n - 2)
# 打印前20个斐波那契数
for i in range(1, 21):
print(fib(i))
需要提醒大家,上面计算斐波那契数的代码虽然看起来非常简单明了,但执行性能是比较糟糕的,原因大家可以自己思考一下,更好的做法还是之前讲过的使用循环递推的方式,代码如下所示。
def fib(n):
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return a
四、常见Python内置函数
1.map函数
map函数会根据提供的函数对指定的序列做 映射
。
map函数的定义如下:
map(function, iterable,…)
第1个参数是函数的名称;第2个参数表示支持迭代的容器或者迭代器。
map函数的作用是以参数序列中的每个元素分别调用function函数,把每次调用后返回的结果保存为对象。
func = lambda x:x+2
result = map(func, [1,2,3,4,5])
print(list(result))
2.filter函数
filter函数会对指定序列执行 过滤操作
。
filter函数的定义如下:
filter(function,iterable)
第1个参数可以是函数的名称;第2个参数表示的是序列、支持迭代的容器或迭代器。
func = lambda x:x%2
result = filter(func, [1, 2, 3, 4, 5])
print(list(result))
装饰器是Python中的特色语法,可以通过装饰器来增强现有的函数,这是一种非常有用的编程技巧。一些复杂的问题用函数递归调用的方式写起来真的很简单,但是函数的递归调用一定要注意收敛条件和递归公式,找到递归公式才有机会使用递归调用,而收敛条件确定了递归什么时候停下来。