今天我们继续聊聊Python相关的一些面试题目,前面的文章已经提到几个比较基础的问题,比如 Python 的特点、List 和 Tuple 的区别、深拷贝与浅拷贝、GIL 以及字典为什么这么快。
这一篇继续整理三个 Python 面试知识点:装饰器、生成器以及迭代器机制。这三个概念很多人刚学 Python 时都会觉得有点抽象,但其实理解之后,会发现它们是 Python 非常优雅的一套设计。
当然,以下内容仅是博主的个人理解与整理,如果有误,欢迎指正。
一、Python装饰器
Python的装饰器是什么?
简单说,
装饰器本质上就是一个函数 ,用来"包装 "另一个函数,从而在不修改原函数代码的情况下给它增加功能。
比如
python
def log(func):
def wrapper():
print("start")
func()
print("end")
return wrapper
这里的 log 就是一个装饰器函数。它接收一个函数 func 作为参数,然后在内部定义了一个新的函数 wrapper。在 wrapper 中,我们在函数执行前后分别打印了一些信息。
比如我们有一个函数如下
python
def say_hello():
print("hello")
现在希望在执行这个函数时打印一些日志,如果不用装饰器,可能会这样写:
python
def say_hello():
print("start")
print("hello")
print("end")
问题是,如果有很多函数都需要这样的功能,我们就要在每个函数里重复写同样的代码,这显然不太优雅。
装饰器就是为了解决这个问题。我们可以这样使用它:
python
@log
def say_hello():
print("hello")
这样当调用这个函数时,就可以实现前面的打印效果
很多初学者第一次看到 @log 时会觉得有点陌生,其实 Python 在背后做的事情很简单,本质上相当于:
python
say_hello = log(say_hello)
也就是说,装饰器只是把原函数传进去,然后返回一个新的函数。
在实际开发中,装饰器的应用非常广泛。例如:
-
Web 框架中的 路由
-
权限校验
-
日志记录
-
性能统计
-
缓存处理
如果你用过 Flask,就会看到类似这样的代码:
python
@app.route("/")
def index():
return "Hello"
这里的 @app.route 本质上就是一个装饰器。
所以从设计角度来看,装饰器其实是一种非常典型的 AOP(面向切面编程)思想。
二、Python生成器
生成器是什么?
在 Python 中,生成器是一种可以按需生成数据的对象 。它最大的特点就是:不会一次性把所有数据都生成出来,而是需要的时候才生成。
生成器通常和一个关键字一起出现,那就是 yield。
我们先看一个简单的例子。
python
def my_generator():
yield 1
yield 2
yield 3
当我们调用这个函数时
python
g = my_generator()
此时并不会立刻执行函数,而是返回一个 生成器对象。
当我们逐个取值时:
python
print(next(g))
print(next(g))
print(next(g))
"""
1
2
3
"""
每调用一次 next(),函数就会执行到下一个 yield 位置,并返回对应的值。
如果你熟悉普通函数,可以把 yield 理解为一种 "暂停并返回" 的机制。函数运行到 yield 时会暂停,下次再继续从这个位置往下执行。
生成器最大的优势是 节省内存。
举个很典型的例子。如果我们想生成一千万个数字:
python
nums = [i for i in range(10000000)]
这会在内存中一次性创建一个非常大的列表。
而如果用生成器:
python
nums = (i for i in range(10000000))
此时 Python 不会真正创建这一千万个数字,而是 用一个生成器按需生成。只有当你遍历的时候,数据才会逐个产生。
因此生成器特别适合处理:
-
大数据集
-
流式数据
-
文件读取
-
网络数据流
例如 Python 中非常经典的写法:
python
for line in open("file.txt"):
print(line)
其实文件对象本身就是一个 迭代器 + 生成器式读取,每次只读取一行,而不是一次把整个文件读入内存。
三、Python 迭代器和可迭代对象
很多人在学习 Python 时会遇到两个很像的概念:
可迭代对象(Iterable)
迭代器(Iterator)
这两个概念其实是 Python 迭代机制的核心。
简单来说,可迭代对象就是"可以被遍历的对象"。
例如我们经常写:
python
for i in [1,2,3]:
print(i)
这里的列表 [1,2,3] 就是一个可迭代对象。
Python 中很多数据结构都是可迭代的,比如:
-
list
-
tuple
-
dict
-
string
-
set
只要一个对象可以被 for 循环遍历,我们通常就可以认为它是可迭代对象。
从实现角度来说,一个对象只要实现了 __iter__() 方法,就可以被称为可迭代对象。
不过 for 循环真正工作的对象,其实并不是可迭代对象本身,而是 迭代器。
迭代器是一种更底层的对象,它需要实现两个方法:
__next__() 的作用就是返回下一个元素,如果数据已经遍历完,就会抛出 StopIteration 异常。
举个简单例子,我们可以手动创建一个迭代器:
python
nums = [1,2,3]
it = iter(nums)
print(next(it))
print(next(it))
print(next(it))
输出:1 2 3
这里的流程其实是:
-
iter(nums)把列表变成一个迭代器 -
next(it)每次获取下一个元素
而 for 循环其实就是帮我们自动完成了这些步骤。Python 在内部大致做的是这样的事情:
python
it = iter(nums)
while True:
try:
item = next(it)
print(item)
except StopIteration:
break
所以总结一句话:
可迭代对象是"可以被遍历的对象",而迭代器是"真正执行遍历动作的对象"。
另外,前面提到的 生成器其实也是一种特殊的迭代器 。只要函数中包含 yield,Python 就会自动帮我们创建一个迭代器对象。
总结
本文我们主要整理了 Python 面试中另外三个非常经典的问题。
装饰器 其实就是一个函数包装器,它可以在不修改 原函数代码的情况下扩展功能,在 Web 框架、日志记录和权限控制中都有大量应用。
生成器 则是一种非常优雅的数据生成方式,通过 yield 按需产生数据 ,可以大幅节省内存,非常适合处理大规模数据或流式数据。
而迭代器和可迭代对象 则构成了 Python 的整个迭代机制。像 for 循环、生成器、文件读取等功能,本质上都建立在这套机制之上。
理解了这些概念之后,你会发现 Python 在很多设计上其实都非常统一。
