如何使用闭包方法
本篇内容介绍了"如何使用闭包方法"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
什么是 Python 闭包?
首先,让我使用一个简单的示例来说明什么是 Python 中的闭包。看下面的函数:
def outer(): x = 1 def inner(): print(f'x in outer function: {x}') return inner
在一个函数内部定义另外一个函数,并返回这个函数,这种特性就是闭包。检查 outer 函数的返回值,可以确认这是一个函数。
>>> def outer(): ... x = 1 ... def inner(): ... print(f'x in outer function: {x}') ... return inner ... >>> outer>>> outer() .inner at 0x7fb2ecdaca60> >>>
闭包这种特性能做什么呢?因为函数返回的是一个函数,我们就可以调用这个函数,比如:
>>> outer()() x in outer function: 1 >>>
不过我们一般会这么使用闭包,这样太丑陋了。你可能会好奇这个跟递归有什么关系?别着急,让我们慢慢体会闭包的牛逼之处。
闭包内的变量访问
从前述的运行结果来看,inner 函数可以访问 outer 函数内部定义的变量 x,但是却无法修改它,下面的代码运行时会报错:
>>> def outer(): ... x = 1 ... def inner(): ... print(f'x in outer function (before modifying): {x}') ... x += 1 ... print(f'x in outer function (after modifying): {x}') ... return inner ... >>> f = outer() >>> f() Traceback (most recent call last): File "", line 1, in File " ", line 4, in inner UnboundLocalError: local variable 'x' referenced before assignment >>>
为了解决这个问题,我们可以加上 nonlocal 关键字,告诉 inner 函数,这不是一个本地变量:
>>> def outer(): ... x = 1 ... def inner(): ... nonlocal x ... print(f'x in outer function (before modifying): {x}') ... x += 1 ... print(f'x in outer function (after modifying): {x}') ... return inner ... >>> >>> f = outer() >>> f() x in outer function (before modifying): 1 x in outer function (after modifying): 2 >>> f() x in outer function (before modifying): 2 x in outer function (after modifying): 3 >>> f() x in outer function (before modifying): 3 x in outer function (after modifying): 4 >>>
有没有发现,x 的值竟然被保存了下来,每次调用一下,就增加了 1,这就是闭包的妙处。
用闭包来替换递归
利用上述闭包会保留调用结果的特性,我们可以用这个来替换递归,比如利用闭包计算斐波那契数列:
def fib(): x1 = 0 x2 = 1 def get_next_number(): nonlocal x1, x2 x3 = x1 + x2 x1, x2 = x2, x3 return x3 return get_next_number
可以这样调用来生产斐波那契数列:
>>> def fib(): ... x1 = 0 ... x2 = 1 ... def get_next_number(): ... nonlocal x1, x2 ... x3 = x1 + x2 ... x1, x2 = x2, x3 ... return x3 ... return get_next_number ... >>> fibonacci = fib() >>> for i in range(2, 21): ... num = fibonacci() ... print(f'The {i}th Fibonacci number is {num}') ... The 2th Fibonacci number is 1 The 3th Fibonacci number is 2 The 4th Fibonacci number is 3 The 5th Fibonacci number is 5 The 6th Fibonacci number is 8 The 7th Fibonacci number is 13 The 8th Fibonacci number is 21 The 9th Fibonacci number is 34 The 10th Fibonacci number is 55 The 11th Fibonacci number is 89 The 12th Fibonacci number is 144 The 13th Fibonacci number is 233 The 14th Fibonacci number is 377 The 15th Fibonacci number is 610 The 16th Fibonacci number is 987 The 17th Fibonacci number is 1597 The 18th Fibonacci number is 2584 The 19th Fibonacci number is 4181 The 20th Fibonacci number is 6765 >>>
而使用递归方法计算斐波那契数列的方法如下所示:
def fib_recursion(n:int) -> int: if n <= 1: return n return fib_recursion(n-1) + fib_recursion(n-2)
把之前的闭包版本封装一下:
def fib(): x1 = 0 x2 = 1 def get_next_number(): nonlocal x1, x2 x3 = x1 + x2 x1, x2 = x2, x3 return x3 return get_next_number def fib_closure(n): f = fib() for i in range(2, n+1): num = f() return num
这样使用 fib_closure(20) 就可以计算出结果:
In [4]: fib_closure(20) Out[4]: 6765 In [5]: fib_recursion(20) Out[5]: 6765 In [6]:
现在使用 IPython 来测试下这两者的性能:
In [6]: %time fib_closure(20) CPU times: user 10 µs, sys: 1e+03 ns, total: 11 µs Wall time: 14.1 µs Out[6]: 6765 In [7]: %time fib_recursion(20) CPU times: user 2.76 ms, sys: 15 µs, total: 2.78 ms Wall time: 2.8 ms Out[7]: 6765
可以看出两差相差近 1000 倍,这还只是计算到第 20 个数的情况下,如果计算到 100,那使用递归会计算很久甚至无法计算出来。
闭包的其他用处
Python 的闭包不仅仅用于替换递归,还有很多场景可以使用闭包。比如学生成绩的分类函数:
学生成绩数据:
students = { 'Alice': 98, 'Bob': 67, 'Chris': 85, 'David': 75, 'Ella': 54, 'Fiona': 35, 'Grace': 69 }
现在需要根据学生成绩进行分类,通常情况下我们会写多个函数来进行分类,而分类的标准又会经常变化,这时候闭包就很方便了:
def make_student_classifier(lower_bound, upper_bound): def classify_student(exam_dict): return {k:v for (k,v) in exam_dict.items() if lower_bound <= v < upper_bound} return classify_student grade_A = make_student_classifier(80, 100) grade_B = make_student_classifier(70, 80) grade_C = make_student_classifier(50, 70) grade_D = make_student_classifier(0, 50)
如果分类标准变化,直接个性函数的参数即可,主要代码逻辑不变,如果想查找成绩分类为 A 的学生,只需要调用 grade_A(students) 即可:
In [13]: grade_A(students) Out[13]: {'Alice': 98, 'Chris': 85}
闭包使用上述分类函数很容易修改且更加易读。
"如何使用闭包方法"的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注网站,小编将为大家输出更多高质量的实用文章!