Python高级特性详解:闭包、迭代器与生成器的原理及高效应用实践
目录
闭包
闭包的定义
闭包满足的条件
闭包示例
闭包的应用场景
装饰器模式
装饰器的概念
装饰器的框架
装饰器的调用方式
1、直接调用方式:outer_function(待执行的函数名)()
2、语法糖调用方式:在待执行的函数上标明装饰器函数名@装饰器函数名
有参数函数的装饰
带参数的装饰器
迭代器
可迭代对象
判断是否可迭代以及是否是迭代器
__iter__()和__next__()方法
生成器
生成器的定义
生成器的创建方式
1、通过生成器函数:
2、通过生成器推导式:
生成器的输出方式
next和return的区别
send和next的区别
在 Python 编程的世界里,闭包、迭代器和生成器是极为重要且强大的概念。它们不仅体现了 Python 语言的灵活性和高效性,还为开发者提供了更加优雅、简洁的编程方式。本文将深入探讨这三个概念,通过丰富的代码示例帮助读者透彻理解其原理与应用。
闭包
闭包的定义
闭包是指内层函数对外层函数(非全局)变量的引用。这一特性使得局部变量能够常驻内存,同时还能防止其他程序随意修改该变量。从更严格的定义来说,如果内层函数引用了外层函数的变量(包括参数),并且外层函数返回内层函数名,这种函数架构就称为闭包。
闭包满足的条件
-
内层函数定义嵌套在外层函数中:这是闭包形成的结构基础,内层函数依赖于外层函数的作用域。
-
内层函数引用外层函数的变量:内层函数通过这种引用,获取了外层函数作用域中的数据,从而形成了一个封闭的环境。
-
外层函数返回内层函数名:通过返回内层函数名,使得在外部可以调用内层函数,并且能够访问到内层函数所引用的外层函数变量。
闭包示例
def f1(lst):
def f2():
for item in lst:
print(item)
return f2
在上述代码中,f2是内层函数,它引用了外层函数f1的参数lst。f1函数返回了f2函数名。当我们调用f1时,例如:
kist = [1, 2, 3]
x = f1(kist)
x()
先调用了f1(kist),得到了返回值f2的名字,将其赋值给x。输出x(),相当于给得到的f2的名字加小括号,即为调用了f2,所以可以得到f2中的内容,即打印出列表kist中的元素。
闭包的应用场景
闭包在很多场景中都有广泛应用,比如实现函数的状态保持。例如,我们可以通过闭包来实现一个简单的计数器:
def counter():
count = 0
def inner():
nonlocal count
count += 1
return count
return inner
在这个例子中,inner函数形成了闭包,它引用了外层函数counter的变量count。每次调用inner函数,count的值都会增加并返回,从而实现了一个简单的计数器功能。
装饰器模式
装饰器的概念
装饰器本质上是一个函数,它能够在不修改其他函数代码的情况下,为这些函数增加额外的功能。装饰器可以看作是一个以函数作为参数并返回一个替换函数的可执行函数。其本质是一个嵌套函数,外层函数的参数是被执行的函数,内层函数是一个闭包并在其中增加新功能(装饰器的功能函数)。
装饰器的框架
def outer_function(func):
def inner_function():
# 增加业务逻辑
print("在函数执行前执行的代码")
func()
# 增加业务逻辑
print("在函数执行后执行的代码")
return inner_function
装饰器的调用方式
1、直接调用方式:outer_function(待执行的函数名)()
例如,我们有一个简单的函数say_hello:
def say_hello():
print("Hello!")
decorated_say_hello = outer_function(say_hello)
decorated_say_hello()
2、语法糖调用方式:在待执行的函数上标明装饰器函数名@装饰器函数名
@outer_function
def say_hello():
print("Hello!")
say_hello()
这两种方式的效果是一样的,语法糖的方式更加简洁明了,是 Python 中常用的装饰器调用方式。
有参数函数的装饰
当被装饰的函数有参数时,装饰器函数中的内层函数也需要包含对应的参数。例如:
def outer_function(func):
def inner_function(a, b):
print("在函数执行前执行的代码")
result = func(a, b)
print("在函数执行后执行的代码")
return result
return inner_function
@outer_function
def add(a, b):
return a + b
print(add(2, 3))
带参数的装饰器
带参数的装饰器由 3 个函数嵌套而成。最外层函数有一个装饰器自带的参数,内层函数不变,相当于闭包的嵌套。例如,我们想要一个可以控制日志级别(比如只在特定级别打印日志)的装饰器:
def log_level(level):
def outer_function(func):
def inner_function(*args, **kwargs):
if level == "debug":
print("进入调试模式")
result = func(*args, **kwargs)
if level == "debug":
print("调试模式结束")
return result
return inner_function
return outer_function
@log_level(level="debug")
def calculate(a, b):
return a * b
print(calculate(4, 5))
在这个例子中,log_level是最外层函数,接受一个参数level。outer_function接受被装饰的函数func,inner_function是真正执行装饰逻辑的闭包函数。通过这种方式,我们实现了一个带参数的装饰器。
迭代器
可迭代对象
在 Python 中,常见的可迭代对象有str、list、tuple、set、dict等。可迭代对象是指那些可以使用for循环进行遍历的对象。
判断是否可迭代以及是否是迭代器
我们可以使用collections.abc模块中的Iterable和Iterator来判断一个对象是否是可迭代对象或迭代器。例如:
from collections.abc import Iterable, Iterator
my_list = [1, 2, 3]
print(isinstance(my_list, Iterable)) # True
print(isinstance(my_list, Iterator)) # False
这里,my_list是一个可迭代对象,但不是一个迭代器。
__iter__()和__next__()方法
1、__iter__():调用该方法会返回一个迭代器对象。例如:
my_list = [1, 2, 3]
my_iterator = my_list.__iter__()
print(isinstance(my_iterator, Iterator)) # True
2、__next__():该方法用于从迭代器中逐个取出数据。例如:
my_list = [1, 2, 3]
my_iterator = iter(my_list)
print(next(my_iterator)) # 1
print(next(my_iterator)) # 2
print(next(my_iterator)) # 3
# print(next(my_iterator)) # 抛出StopIteration异常
当迭代器中没有更多数据时,继续调用__next__()方法会抛出StopIteration异常。
生成器
生成器的定义
如果一个函数中包含yield关键字,那么这个函数就称为生成器函数。生成器函数与普通函数的执行流程有所不同,当执行到yield语句时,函数会暂停执行,并返回一个值,下次调用时会从暂停的地方继续执行。
生成器的创建方式
1、通过生成器函数:
def my_generator():
yield 1
yield 2
yield 3
调用这个生成器函数会返回一个生成器对象:
gen = my_generator()
print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # 3
# print(next(gen)) # 抛出StopIteration异常
2、通过生成器推导式:
gen = (i for i in range(1, 4))
print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # 3
# print(next(gen)) # 抛出StopIteration异常
生成器推导式与列表生成式的语法类似,只是将方括号变成了圆括号。通过print的结果可以看出这是个生成器,所以可以使用next来一个一个输出结果。用next一个一个输出,可以想输出几个就输出几个。
生成器的输出方式
1、__next__()方法:通过next()函数调用生成器的__next__()方法来获取生成器的下一个值。要注意,它的本质是读取到下一个位置有东西存在的话,就输出本位置的数据,所以如果超出了范围就会报错。
2、send()方法:send()方法不仅可以让生成器向下执行一次,还可以给上一个yield的位置传递值。但是需要注意,第一个元素不能用send,因为它没有上一个元素来接受值。例如:
def my_generator():
value = yield 1
yield value + 1
gen = my_generator()
print(next(gen)) # 1
print(gen.send(2)) # 3
在这个例子中,send(2)将值2传递给了上一个yield的位置,即value变量。
3. 用for循环:可以直接使用for循环来遍历生成器,这种方式更加简洁:
def my_generator():
for i in range(1, 4):
yield i
gen = my_generator()
for num in gen:
print(num) # 依次输出1, 2, 3
4、转换为列表:可以使用list()函数将生成器转换为列表,一次性获取生成器中的所有值。但是需要注意,这种方式会将生成器中的所有元素加载到内存中,如果生成器中的元素数量非常大,可能会占用大量内存。例如:
def my_generator():
for i in range(1, 4):
yield i
gen = my_generator()
my_list = list(gen)
print(my_list) # [1, 2, 3]
next和return的区别
1、使用return语句的函数会一次性返回所有结果,这可能会占用大量内存,尤其是当返回的数据量很大时。
2、生成器使用yield语句,每次调用next()时生成一个值,用多少生成多少。生成器的指针会一个一个地指向下一个,不会回头,next()到哪,指针就指到哪儿,下一次继续获取指针指向的值。这种方式在处理大数据集时更加高效,因为它不需要一次性将所有数据加载到内存中。
send和next的区别
1、send和next()都是让生成器向下走一次。
2、send可以给上一个yield的位置传递值,但是不能给最后一个yield发送值。在第一次执行生成器代码的时候不能使用send()。
通过对闭包、迭代器和生成器的深入理解,我们可以在 Python 编程中更加灵活、高效地解决各种问题,编写出更加优雅、简洁的代码。希望本文能帮助读者更好地掌握这些重要的概念,并在实际编程中发挥它们的强大作用。
作者:Python智慧行囊