Python学习笔记(十) 迭代器、生成器、装饰器

简介: Python学习笔记(十) 迭代器、生成器、装饰器

这篇文章主要介绍 Python 中几个常用的高级特性,用好这几个特性可以让自己的代码更加 Pythonic 哦



1、迭代器


什么是迭代呢?简单来说,迭代是访问元素集合的一种方式,关于迭代有两个概念需要我们理解:

  • 可迭代对象 (Iterable):一个实现了 __iter__() 方法的对象
  • 迭代器对象 (Iterator):一个实现了 __iter__()__next__() 方法的对象


由此我们知道,迭代器对象一定是可迭代对象,而可迭代对象不一定是迭代器对象,例如:

列表类型、集合类型是可迭代对象 (有实现 __iter__()),但不是迭代器对象 (没实现 __next__())

而生成器、文件对象是可迭代对象 (有实现 __iter__()),且也是迭代器对象 (有实现 __next__())

from collections.abc import Iterable # 可迭代对象
from collections.abc import Iterator # 迭代器对象
li = [i for i in range(2)]
print(isinstance(li, Iterable)) # True
print(isinstance(li, Iterator)) # False
di = dict()
print(isinstance(di, Iterable)) # True
print(isinstance(di, Iterator)) # False
ge = (i for i in range(2))
print(isinstance(ge, Iterable)) # True
print(isinstance(ge, Iterator)) # True
fi = open('File', 'wt')
print(isinstance(fi, Iterable)) # True
print(isinstance(fi, Iterator)) # True

一般来说,人们会使用 for 循环和 next() 方法遍历可迭代对象和迭代器对象

for 循环可作用于可迭代对象和迭代器对象,而 next() 方法只能作用于迭代器对象

注意,迭代器对象只能迭代一次,并且在迭代结束时,会抛出 StopIteration 异常以表示迭代终止

# 使用 for 循环
li = [i for i in range(2)]
ge = (i for i in range(2))
for i in li:
    print(i, end = ' ') # 0 1 
# for 能自动捕获并处理 StopIteration 异常,以判断迭代终止
for i in ge:
    print(i, end = ' ') # 0 1 


# 使用 next 方法
li = [i for i in range(2)]
ge = (i for i in range(2))
print(next(li)) # TypeError: 'list' object is not an iterator
# next 每次往前读取一个数据,直至到达末尾时抛出 StopIteration 异常
print(next(ge)) # 0
print(next(ge)) # 1
print(next(ge)) # StopIteration


2、生成器


在上面我们可以知道,使用列表生成式可以快速创建一个列表:

li = [i for i in range(100000)]
sys.getsizeof(li) # 412236


但是这样有一个缺点,那就是它会直接生成列表中的所有元素,占用大量内存空间

而使用生成器就能解决这个问题,生成器并不会直接生成全部元素,而是保存一个生成元素的算法

当需要返回结果的时候,生成器才会去计算该元素的值并返回,这种特性也被称为惰性计算或延迟计算

ge = (i for i in range(100000))
sys.getsizeof(ge) # 64


(1)创建生成器


一是使用生成器表达式,它类似于列表生成式,只需将列表生成式中的中括号替换成小括号即可

ge = (i for i in range(5))
print(type(ge)) # <class 'generator'>


二是使用生成器函数,它类似于普通函数,只需将普通函数中的 return 语句替换成 yield 语句即可

def counter(max_num):
    count = 0
    while count < max_num:
        yield count
        count += 1
ge = counter(5)
print(type(ge)) # <class 'generator'>


我们需要将生成器函数的调用赋值给一个变量,那么该变量就是一个生成器

每次调用生成器获取一个元素时,都会执行一次生成器函数中的代码,直至遇到 yield 或者 return 语句

  • yield   语句会返回一个值并挂起函数执行,下一次再调用生成器时会从当前位置继续执行
  • return 语句会马上终止生成器的执行,抛出 StopIteration 异常


(2)调用生成器


一是使用 next() 全局方法,每次调用 next() 方法都会返回一个数据,直至调用结束

ge = (i for i in range(2))
print(next(ge)) # 0
print(next(ge)) # 1
print(next(ge)) # StopIteration


二是使用 send() 内置方法,每次调用 send() 方法都会返回一个数据,并允许向生成器内部发送信息

值得注意的是,第一次调用时需要使用 next() 或者 send(None),不能使用 send() 发送一个其它值

def counter(max_num):
    count = 0
    while count < max_num:
        msg = yield count
        print(msg)
        count += 1
ge = counter(3)
print(ge.send(None))
# 0
print(ge.send('Hello'))
# Hello
# 1
print(ge.send('World'))
# World
# 2
print(ge.send('!'))
# StopIteration


三是使用 for 循环,实际上生成器也是一种特殊的迭代器

ge = (i for i in range(2))
for i in ge:
    print(i)
# 0
# 1


3、装饰器


在讲解装饰器前,我们先来解释一下什么是闭包?先看闭包的定义:

如果在一个内部函数中对外部作用域(但不是全局作用域)的变量进行引用,那么该内部函数称为闭包

def external(x):
    def internal(y):
        return x + y
    return internal
func1 = external(5)
print(func1(10)) # 15
func2 = external(10)
print(func2(10)) # 20
# 以上面的代码为例,internal 是内部函数,external 是外部函数
# 在内部函数 internal 中对外部作用域(但不是全局作用域)的变量 x 进行引用
# 那么这时我们可以称内部函数 internal 为闭包

装饰器实际上就是一个闭包,它接收一个函数作为参数,返回一个经过装饰的函数

def decorator(func):
    def wrapper(*args, **kwargs):
        print('装饰器 前处理')
        func()
        print('装饰器 后处理')
    return wrapper
@decorator
def func():
    print('原有操作')
func()
# 装饰器 前处理
# 原有操作
# 装饰器 后处理


实际上,我们还可以为一个函数添加多个装饰器,注意观察它们之间的执行顺序:

def decorator1(func):
    def wrapper(*args, **kwargs):
        print('装饰器1 前处理')
        func()
        print('装饰器1 后处理')
    return wrapper
def decorator2(func):
    def wrapper(*args, **kwargs):
        print('装饰器2 前处理')
        func()
        print('装饰器2 后处理')
    return wrapper
@decorator1
@decorator2
def func():
    print('原有操作')
func()
# 装饰器1 前处理
# 装饰器2 前处理
# 原有操作
# 装饰器2 后处理
# 装饰器1 后处理


装饰器常常用于日志功能,下面是一个例子:

import time
from functools import wraps
def logger(func):
    @wraps(func) # 添加 functools.wraps 可以防止原有函数自身的信息丢失
    def wrapper(*args, **kwargs):
        currTime = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
        print(
            '[%s] %s is called, with parameters %s, %s' %
            (currTime, func.__name__, args, kwargs)
        )
        return func(*args, **kwargs)
    return wrapper
@logger
def func(x, y):
   return x + y
res = func(3, 4)
# [2021-03-12 11:50:33] func is called, with parameters (3, 4), {}


装饰器还常用于计时功能,下面是一个例子:

import time
from functools import wraps
def timer(func):
    @wraps(func) # 添加 functools.wraps 可以防止原有函数自身的信息丢失
    def wrapper(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)
        end_time = time.time()
        print(end_time - start_time)
    return wrapper
@timer
def func():
    time.sleep(1)
func()
# 1.0003883838653564



文章知识点与官方知识档案匹配,可进一步学习相关知识

目录
相关文章
|
4月前
|
测试技术 Python
Python装饰器:为你的代码施展“魔法”
Python装饰器:为你的代码施展“魔法”
306 100
|
5月前
|
设计模式 缓存 监控
Python装饰器:优雅增强函数功能
Python装饰器:优雅增强函数功能
309 101
|
4月前
|
缓存 Python
Python装饰器:为你的代码施展“魔法
Python装饰器:为你的代码施展“魔法
205 88
|
5月前
|
存储 缓存 测试技术
Python装饰器:优雅地增强函数功能
Python装饰器:优雅地增强函数功能
243 98
|
5月前
|
缓存 测试技术 Python
解锁Python超能力:深入理解装饰器
解锁Python超能力:深入理解装饰器
157 2
|
5月前
|
存储 大数据 Unix
Python生成器 vs 迭代器:从内存到代码的深度解析
在Python中,处理大数据或无限序列时,迭代器与生成器可避免内存溢出。迭代器通过`__iter__`和`__next__`手动实现,控制灵活;生成器用`yield`自动实现,代码简洁、内存高效。生成器适合大文件读取、惰性计算等场景,是性能优化的关键工具。
310 2
|
存储 Linux 索引
python基础学习笔记
服务器 1.ftp服务器         FTP是FileTransferProtocol(文件传输协议)的英文简称,中文名称为“文传协议”。
1802 0
|
数据安全/隐私保护 Python

推荐镜像

更多