今天我们继续聊聊Python相关的一些面试题目,前面的文章已经提到几个比较基础的问题,比如 Python 的特点、List 和 Tuple 的区别、深拷贝与浅拷贝、GIL 以及字典为什么这么快。

这一篇继续整理三个 Python 面试知识点:装饰器、生成器以及迭代器机制。这三个概念很多人刚学 Python 时都会觉得有点抽象,但其实理解之后,会发现它们是 Python 非常优雅的一套设计。

当然,以下内容仅是博主的个人理解与整理,如果有误,欢迎指正。

一、Python装饰器

Python的装饰器是什么?

简单说,

装饰器本质上就是一个函数,用来“包装”另一个函数,从而在不修改原函数代码的情况下给它增加功能

比如

def log(func):
    def wrapper():
        print("start")
        func()
        print("end")
    return wrapper

这里的 log 就是一个装饰器函数。它接收一个函数 func 作为参数,然后在内部定义了一个新的函数 wrapper。在 wrapper 中,我们在函数执行前后分别打印了一些信息。

比如我们有一个函数如下

def say_hello():
    print("hello")

现在希望在执行这个函数时打印一些日志,如果不用装饰器,可能会这样写:

def say_hello():
    print("start")
    print("hello")
    print("end")

问题是,如果有很多函数都需要这样的功能,我们就要在每个函数里重复写同样的代码,这显然不太优雅。

装饰器就是为了解决这个问题。我们可以这样使用它:

@log
def say_hello():
    print("hello")

这样当调用这个函数时,就可以实现前面的打印效果

很多初学者第一次看到 @log 时会觉得有点陌生,其实 Python 在背后做的事情很简单,本质上相当于:

say_hello = log(say_hello)

也就是说,装饰器只是把原函数传进去,然后返回一个新的函数。

在实际开发中,装饰器的应用非常广泛。例如:

  • Web 框架中的 路由

  • 权限校验

  • 日志记录

  • 性能统计

  • 缓存处理

如果你用过 Flask,就会看到类似这样的代码:

@app.route("/")
def index():
    return "Hello"

这里的 @app.route 本质上就是一个装饰器。

所以从设计角度来看,装饰器其实是一种非常典型的 AOP(面向切面编程)思想

二、Python生成器

生成器是什么?

在 Python 中,生成器是一种可以按需生成数据的对象。它最大的特点就是:不会一次性把所有数据都生成出来,而是需要的时候才生成。

生成器通常和一个关键字一起出现,那就是 yield

我们先看一个简单的例子。

def my_generator():
    yield 1
    yield 2
    yield 3

当我们调用这个函数时

g = my_generator()

此时并不会立刻执行函数,而是返回一个 生成器对象

当我们逐个取值时:

print(next(g))
print(next(g))
print(next(g))

"""
1
2
3

"""

每调用一次 next(),函数就会执行到下一个 yield 位置,并返回对应的值。

如果你熟悉普通函数,可以把 yield 理解为一种 “暂停并返回” 的机制。函数运行到 yield 时会暂停,下次再继续从这个位置往下执行。

生成器最大的优势是 节省内存

举个很典型的例子。如果我们想生成一千万个数字:

nums = [i for i in range(10000000)]

这会在内存中一次性创建一个非常大的列表。

而如果用生成器:

nums = (i for i in range(10000000))

此时 Python 不会真正创建这一千万个数字,而是 用一个生成器按需生成。只有当你遍历的时候,数据才会逐个产生。

因此生成器特别适合处理:

  • 大数据集

  • 流式数据

  • 文件读取

  • 网络数据流

例如 Python 中非常经典的写法:

for line in open("file.txt"):
    print(line)

其实文件对象本身就是一个 迭代器 + 生成器式读取,每次只读取一行,而不是一次把整个文件读入内存。

三、Python 迭代器和可迭代对象

很多人在学习 Python 时会遇到两个很像的概念:

可迭代对象(Iterable)
迭代器(Iterator)

这两个概念其实是 Python 迭代机制的核心。

简单来说,可迭代对象就是“可以被遍历的对象”。

例如我们经常写:

for i in [1,2,3]:
    print(i)

这里的列表 [1,2,3] 就是一个可迭代对象。

Python 中很多数据结构都是可迭代的,比如:

  • list

  • tuple

  • dict

  • string

  • set

只要一个对象可以被 for 循环遍历,我们通常就可以认为它是可迭代对象。

从实现角度来说,一个对象只要实现了 __iter__() 方法,就可以被称为可迭代对象。

不过 for 循环真正工作的对象,其实并不是可迭代对象本身,而是 迭代器

迭代器是一种更底层的对象,它需要实现两个方法:

__next__() 的作用就是返回下一个元素,如果数据已经遍历完,就会抛出 StopIteration 异常。

举个简单例子,我们可以手动创建一个迭代器:

nums = [1,2,3]

it = iter(nums)

print(next(it))
print(next(it))
print(next(it))

输出:1 2 3

这里的流程其实是:

  1. iter(nums) 把列表变成一个迭代器

  2. next(it) 每次获取下一个元素

for 循环其实就是帮我们自动完成了这些步骤。Python 在内部大致做的是这样的事情:

it = iter(nums)

while True:
    try:
        item = next(it)
        print(item)
    except StopIteration:
        break

所以总结一句话:

可迭代对象是“可以被遍历的对象”,而迭代器是“真正执行遍历动作的对象”。

另外,前面提到的 生成器其实也是一种特殊的迭代器。只要函数中包含 yield,Python 就会自动帮我们创建一个迭代器对象。


总结

本文我们主要整理了 Python 面试中另外三个非常经典的问题。

装饰器其实就是一个函数包装器,它可以在不修改原函数代码的情况下扩展功能,在 Web 框架、日志记录和权限控制中都有大量应用。

生成器则是一种非常优雅的数据生成方式,通过 yield 按需产生数据,可以大幅节省内存,非常适合处理大规模数据或流式数据。

迭代器和可迭代对象则构成了 Python 的整个迭代机制。像 for 循环、生成器、文件读取等功能,本质上都建立在这套机制之上。

理解了这些概念之后,你会发现 Python 在很多设计上其实都非常统一。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐